From aebc846a369ee9394ca1250ee7741d188011cef9 Mon Sep 17 00:00:00 2001 From: //AM Date: Fri, 31 Jul 2026 16:28:54 -0400 Subject: [PATCH 1/3] research: add Morocco official-source preflight --- .../ACQUISITION-CONSTRAINTS-v0.1.json | 11 + .../ACQUISITION-CONSTRAINTS-v0.1.md | 21 ++ .../MISSION-SELECTION-SCORECARD-v0.1.json | 13 + .../MISSION-SELECTION-SCORECARD-v0.1.md | 17 + .../NORMATIVE-SOURCE-MAP-v0.1.json | 14 + .../NORMATIVE-SOURCE-MAP-v0.1.md | 22 ++ .../OFFICIAL-SOURCE-REGISTER-v0.1.json | 177 ++++++++++ .../OFFICIAL-SOURCE-REGISTER-v0.1.md | 27 ++ .../PMP-MISSION-CANDIDATES-v0.1.json | 60 ++++ .../PMP-MISSION-CANDIDATES-v0.1.md | 42 +++ .../PREFLIGHT-DECISION-v0.1.json | 15 + .../preflight-v0.1/PREFLIGHT-DECISION-v0.1.md | 25 ++ tests/contract/test_morocco_preflight.py | 176 ++++++++++ tools/render_morocco_preflight.py | 311 ++++++++++++++++++ 14 files changed, 931 insertions(+) create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.md create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json create mode 100644 benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md create mode 100644 tests/contract/test_morocco_preflight.py create mode 100644 tools/render_morocco_preflight.py diff --git a/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.json new file mode 100644 index 0000000..faabc2a --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.json @@ -0,0 +1,11 @@ +{ + "document_id": "OAM-MOROCCO-ACQUISITION-CONSTRAINTS-v0.1", + "status": "ENGINEERING_CLASSIFICATIONS_NOT_LEGAL_OPINIONS", + "no_source_bytes_acquired": true, + "constraints": [ + {"scope": "TGR_PDF_SOURCES", "disposition": "LINK_AND_RECEIPT_ONLY", "robots_observation": "No automated crawl executed", "authentication": "None observed", "visible_download": true, "direct_get": "UNTESTED", "human_download": true, "crawl_required": false, "stability": "UUID paths appear stable but bytes not frozen", "blockers": ["rights review", "redirect receipt", "exact-byte freeze authorization"]}, + {"scope": "CDC_CHALLENGE_HTML", "disposition": "LINK_AND_RECEIPT_ONLY", "robots_observation": "Single public pages observed; no crawl", "authentication": "None", "visible_download": false, "direct_get": true, "human_download": true, "crawl_required": false, "stability": "mutable HTML", "blockers": ["terms review", "immutable capture plan"]}, + {"scope": "PMP_CASE_PAGES_AND_ACTION_DOWNLOADS", "disposition": "LINK_AND_RECEIPT_ONLY", "robots_observation": "Public detail pages observed; no automated crawl or artifact action invoked", "authentication": "Detail pages render unauthenticated; artifact actions not tested", "visible_download": true, "direct_get": "DETAIL_PAGE_YES_ARTIFACT_ENDPOINT_UNRESOLVED", "human_download": true, "crawl_required": false, "stability": "stable refConsultation identity; artifact bytes untested", "blockers": ["dynamic artifact URLs", "rights review", "exact-byte freeze authorization"]} + ], + "prohibited": ["authenticated download", "CAPTCHA bypass", "portal crawl", "source-byte commit", "invented legal clearance"] +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.md new file mode 100644 index 0000000..e69ad13 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/ACQUISITION-CONSTRAINTS-v0.1.md @@ -0,0 +1,21 @@ +# Morocco Acquisition Constraints v0.1 + +Status: **ENGINEERING_CLASSIFICATIONS_NOT_LEGAL_OPINIONS** + +No source bytes acquired: **YES** + +## Constraints + +| Scope | Disposition | Robots | Authentication | Visible | Direct GET | Human | Crawl | Stability | Blockers | +|---|---|---|---|---|---|---|---|---|---| +| TGR_PDF_SOURCES | LINK_AND_RECEIPT_ONLY | No automated crawl executed | None observed | YES | UNTESTED | YES | NO | UUID paths appear stable but bytes not frozen | rights review; redirect receipt; exact-byte freeze authorization | +| CDC_CHALLENGE_HTML | LINK_AND_RECEIPT_ONLY | Single public pages observed; no crawl | None | NO | YES | YES | NO | mutable HTML | terms review; immutable capture plan | +| PMP_CASE_PAGES_AND_ACTION_DOWNLOADS | LINK_AND_RECEIPT_ONLY | Public detail pages observed; no automated crawl or artifact action invoked | Detail pages render unauthenticated; artifact actions not tested | YES | DETAIL_PAGE_YES_ARTIFACT_ENDPOINT_UNRESOLVED | YES | NO | stable refConsultation identity; artifact bytes untested | dynamic artifact URLs; rights review; exact-byte freeze authorization | + +## Prohibited + +- authenticated download +- CAPTCHA bypass +- portal crawl +- source-byte commit +- invented legal clearance diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json new file mode 100644 index 0000000..9d62c9a --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json @@ -0,0 +1,13 @@ +{ + "document_id": "OAM-MISSION-SELECTION-SCORECARD-v0.1", + "scale": {"0": "absent", "1": "partial_or_uncertain", "2": "present_and_official"}, + "dimensions": ["stable_official_identity", "french_source_availability", "tender_notice_availability", "dao_specifications_tor", "administrative_or_technical_clauses", "amendment_or_addendum_evidence", "evaluation_criteria_grid", "commission_minute_or_extract", "award_or_result", "programme_previsionnel_linkage", "buyer_procedure_value_date_completeness", "exact_byte_downloadable_artifacts", "no_login_captcha_access_control", "tender_evaluation_award_suitability"], + "scores": [ + {"candidate_id": "PMP-CASE-001", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "PREFERRED_MAR-MISSION-001"}, + {"candidate_id": "PMP-CASE-002", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "FALLBACK"}, + {"candidate_id": "PMP-CASE-003", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "NOT_SELECTED"} + ], + "selection_rule": "Mandatory criteria gate first; these are open cases with official structured notices but no acquired DAO, evaluation or award objects; aggregate score cannot override missing evidence", + "preferred": {"mission_id": "MAR-MISSION-001", "candidate_id": "PMP-CASE-001"}, + "fallback": {"candidate_id": "PMP-CASE-002"} +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md new file mode 100644 index 0000000..0b4b17b --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md @@ -0,0 +1,17 @@ +# Morocco Mission Selection Scorecard v0.1 + +Scores: 0 absent; 1 partial/uncertain; 2 present and official. + +Rule: Mandatory criteria gate first; these are open cases with official structured notices but no acquired DAO, evaluation or award objects; aggregate score cannot override missing evidence + +## Complete scorecard + +| Candidate | stable_official_identity | french_source_availability | tender_notice_availability | dao_specifications_tor | administrative_or_technical_clauses | amendment_or_addendum_evidence | evaluation_criteria_grid | commission_minute_or_extract | award_or_result | programme_previsionnel_linkage | buyer_procedure_value_date_completeness | exact_byte_downloadable_artifacts | no_login_captcha_access_control | tender_evaluation_award_suitability | Total | Mandatory | Nomination | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| PMP-CASE-001 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | PREFERRED_MAR-MISSION-001 | +| PMP-CASE-002 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | FALLBACK | +| PMP-CASE-003 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | NOT_SELECTED | + +Preferred: **MAR-MISSION-001 → PMP-CASE-001** + +Fallback: **PMP-CASE-002** diff --git a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json new file mode 100644 index 0000000..f62ccd0 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json @@ -0,0 +1,14 @@ +{ + "document_id": "OAM-MOROCCO-NORMATIVE-SOURCE-MAP-v0.1", + "status": "PREFLIGHT_NO_LEGAL_INTERPRETATION", + "french_first": true, + "entries": [ + {"source_id": "MAR-NORM-001", "role": "PRIMARY_FRENCH_NORMATIVE_CANDIDATE", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Decree 2-22-431"}, + {"source_id": "MAR-NORM-001-EN", "role": "UNOFFICIAL_TRANSLATION_COMPANION", "authoritative_for_semantics": false, "relationship": "Navigation companion to MAR-NORM-001"}, + {"source_id": "MAR-IMPL-001", "role": "IMPLEMENTING_ORDERS_COMPILATION", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Orders expressly cite Decree 2-22-431"}, + {"source_id": "MAR-CCAG-001", "role": "WORKS_SECTOR_CANDIDATE", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Potentially relevant only to a works case; no applicability conclusion"}, + {"source_id": "CDC-CHALLENGE-001", "role": "MISSION_FRAMING_EN", "authoritative_for_semantics": false, "relationship": "Challenge statement, not normative text"}, + {"source_id": "CDC-CHALLENGE-001-FR", "role": "MISSION_FRAMING_FR", "authoritative_for_semantics": false, "relationship": "French challenge page, not normative text"} + ], + "unresolved": ["Bulletin Officiel chain and amendments", "Current CCAG applicability", "Complete implementing-order inventory"] +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md new file mode 100644 index 0000000..d90d369 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md @@ -0,0 +1,22 @@ +# Morocco Normative Source Map v0.1 + +Status: **PREFLIGHT_NO_LEGAL_INTERPRETATION** + +No legal meaning or applicability conclusion is expressed. + +## Map + +| ID | Role | Authoritative | Relationship | +|---|---|---|---| +| MAR-NORM-001 | PRIMARY_FRENCH_NORMATIVE_CANDIDATE | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Decree 2-22-431 | +| MAR-NORM-001-EN | UNOFFICIAL_TRANSLATION_COMPANION | NO | Navigation companion to MAR-NORM-001 | +| MAR-IMPL-001 | IMPLEMENTING_ORDERS_COMPILATION | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Orders expressly cite Decree 2-22-431 | +| MAR-CCAG-001 | WORKS_SECTOR_CANDIDATE | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Potentially relevant only to a works case; no applicability conclusion | +| CDC-CHALLENGE-001 | MISSION_FRAMING_EN | NO | Challenge statement, not normative text | +| CDC-CHALLENGE-001-FR | MISSION_FRAMING_FR | NO | French challenge page, not normative text | + +## Unresolved + +- Bulletin Officiel chain and amendments +- Current CCAG applicability +- Complete implementing-order inventory diff --git a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json new file mode 100644 index 0000000..a0517bd --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json @@ -0,0 +1,177 @@ +{ + "document_id": "OAM-MOROCCO-OFFICIAL-SOURCE-REGISTER-v0.1", + "status": "PREFLIGHT_ONLY_NO_BYTES_ACQUIRED", + "observed_utc": "2026-07-31T00:00:00Z", + "permitted_domains": ["tgr.gov.ma", "marchespublics.gov.ma", "sgg.gov.ma", "courdescomptes.ma", "govtech.trustvalley.swiss", "worldbank.org"], + "engineering_dispositions": ["PUBLIC_REPO_BYTES_CANDIDATE", "LOCAL_REAL_BYTES_HASHED", "LINK_AND_RECEIPT_ONLY", "BLOCKED_TECHNICALLY", "BLOCKED_ACCESS_CONTROL", "UNRESOLVED"], + "sources": [ + { + "source_id": "MAR-NORM-001", + "complete_title": "Décret n° 2-22-431 du 15 chaabane 1444 (8 mars 2023) relatif aux marchés publics — édition française 2023", + "issuing_authority": "Chef du Gouvernement; publication hosted by Trésorerie Générale du Royaume", + "canonical_url": "https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/Textes%20legislatifs/Decrets/", + "download_url": "https://www.tgr.gov.ma/wps/wcm/connect/1f3081fc-2d01-41de-8339-9a2c7de0480f/DECRET%2B2-22-431%2BFR.pdf?MOD=AJPERES", + "language": "fr", + "authority_rank": "OFFICIAL_GOVERNMENT_HOSTED_NORMATIVE_TEXT", + "publication_or_effective_date": "2023-03-08; portal edition 2023", + "content_type": "application/pdf", + "public_accessibility": "PUBLIC_METADATA_AND_VISIBLE_PDF_LINK", + "login_requirement": "NONE_OBSERVED", + "redirect_behavior": "NOT_BYTE_TESTED_IN_PREFLIGHT", + "expected_byte_format": "PDF", + "translation_status": "ORIGINAL_FRENCH_PUBLICATION", + "proposed_corpus_use": "French-first normative acquisition candidate", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY_PENDING_RIGHTS_AND_BYTE_FREEZE", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "No crawler execution; single canonical link provenance can be recorded without bypass", + "visible_download_mechanism": true, + "one_direct_get_technically_possible": "UNTESTED", + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "LIKELY_UUID_PATH_NOT_VERIFIED", + "provenance_without_bypass": true, + "acquisition_risks": ["Rights and redistribution not reviewed", "Redirect and exact bytes not frozen"], + "unresolved_issues": ["Confirm authoritative publication relationship to Bulletin Officiel before acquisition"] + }, + { + "source_id": "MAR-NORM-001-EN", + "complete_title": "Decree no. 2-22-431 of 15 Chaabane 1444 (8 March 2023) on public procurement", + "issuing_authority": "Trésorerie Générale du Royaume", + "canonical_url": "https://www.tgr.gov.ma/wps/portal/Publication/Brochures%20et%20depliants/", + "download_url": "https://www.tgr.gov.ma/wps/wcm/connect/6afbc2bd-5715-4a68-8afa-0cc19d768173/Decree%2BPublic%2Bprocurement_ENG%2Bversion.pdf?ContentCache=NONE&MOD=AJPERES", + "language": "en", + "authority_rank": "OFFICIAL_GOVERNMENT_HOSTED_COMPANION", + "publication_or_effective_date": "Translation publication date unresolved; underlying decree dated 2023-03-08", + "content_type": "application/pdf", + "public_accessibility": "PUBLIC_METADATA_AND_VISIBLE_PDF_LINK", + "login_requirement": "NONE_OBSERVED", + "redirect_behavior": "NOT_BYTE_TESTED_IN_PREFLIGHT", + "expected_byte_format": "PDF", + "translation_status": "UNOFFICIAL_TRANSLATION_COMPANION", + "proposed_corpus_use": "Non-authoritative English navigation companion only", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY_PENDING_RIGHTS_AND_BYTE_FREEZE", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "No crawler execution; single canonical link provenance can be recorded without bypass", + "visible_download_mechanism": true, + "one_direct_get_technically_possible": "UNTESTED", + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "LIKELY_UUID_PATH_NOT_VERIFIED", + "provenance_without_bypass": true, + "acquisition_risks": ["Must never be promoted to authoritative", "Rights and exact bytes not reviewed"], + "unresolved_issues": ["Publication date and translation provenance need receipt evidence"] + }, + { + "source_id": "MAR-IMPL-001", + "complete_title": "Arrêtés d’application du décret n° 2-22-431 — compilation officielle, édition 2023", + "issuing_authority": "Ministre délégué chargé du budget; hosted by Trésorerie Générale du Royaume", + "canonical_url": "https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/", + "download_url": "https://www.tgr.gov.ma/wps/wcm/connect/69ff3b86-7c5b-46c7-83fb-bce87a5f0774/ARRETE%2Bdecret%2B2-22-431%2BFR.pdf?MOD=AJPERES", + "language": "fr", + "authority_rank": "OFFICIAL_GOVERNMENT_HOSTED_IMPLEMENTING_ORDERS_COMPILATION", + "publication_or_effective_date": "Orders dated 2023-06-09 through 2023-07-03; compilation date unresolved", + "content_type": "application/pdf", + "public_accessibility": "PUBLIC_METADATA_AND_VISIBLE_PDF_LINK", + "login_requirement": "NONE_OBSERVED", + "redirect_behavior": "NOT_BYTE_TESTED_IN_PREFLIGHT", + "expected_byte_format": "PDF", + "translation_status": "ORIGINAL_FRENCH_PUBLICATION", + "proposed_corpus_use": "Implementing-order acquisition candidate; order boundaries must be indexed after byte freeze", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY_PENDING_RIGHTS_AND_BYTE_FREEZE", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "No crawler execution; direct compilation link found through official index/search", + "visible_download_mechanism": true, + "one_direct_get_technically_possible": "UNTESTED", + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "LIKELY_UUID_PATH_NOT_VERIFIED", + "provenance_without_bypass": true, + "acquisition_risks": ["Compilation may contain multiple orders", "Exact order inventory requires non-semantic structural indexing after acquisition authorization"], + "unresolved_issues": ["Confirm complete order list and current amendment status"] + }, + { + "source_id": "MAR-CCAG-001", + "complete_title": "Décret n° 2-14-394 du 6 chaabane 1437 approuvant le cahier des clauses administratives générales applicables aux marchés de travaux", + "issuing_authority": "Chef du Gouvernement; hosted by Trésorerie Générale du Royaume", + "canonical_url": "https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/", + "download_url": "https://www.tgr.gov.ma/wps/wcm/connect/48e2b53e-6fa9-4ee9-a6ae-bbde89b37b6f/2-14-394ccag.pdf?CACHEID=48e2b53e-6fa9-4ee9-a6ae-bbde89b37b6f&MOD=AJPERES", + "language": "fr", + "authority_rank": "OFFICIAL_GOVERNMENT_HOSTED_SECTORAL_TEXT", + "publication_or_effective_date": "2016-05-13; B.O. 6470 dated 2016-06-02", + "content_type": "application/pdf", + "public_accessibility": "PUBLIC_METADATA_AND_VISIBLE_PDF_LINK", + "login_requirement": "NONE_OBSERVED", + "redirect_behavior": "NOT_BYTE_TESTED_IN_PREFLIGHT", + "expected_byte_format": "PDF", + "translation_status": "ORIGINAL_FRENCH_PUBLICATION", + "proposed_corpus_use": "Sectoral candidate only for a works procurement mission; relevance is metadata-level and not a legal applicability conclusion", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY_PENDING_OWNER_SCOPE_AND_RIGHTS", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "No crawler execution; direct official PDF link is visible", + "visible_download_mechanism": true, + "one_direct_get_technically_possible": "UNTESTED", + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "LIKELY_UUID_PATH_NOT_VERIFIED", + "provenance_without_bypass": true, + "acquisition_risks": ["Applicability to a selected mission requires owner/legal confirmation", "Predates Decree 2-22-431"], + "unresolved_issues": ["Confirm current applicability and amendment chain without interpreting policy"] + }, + { + "source_id": "CDC-CHALLENGE-001", + "complete_title": "Morocco — Supreme Audit Institution (Cour des Comptes / CDC): Public Procurement Audit Challenge", + "issuing_authority": "Cour des Comptes / CRC with World Bank, SECO and Trust Valley", + "canonical_url": "https://govtech.trustvalley.swiss/challenges/cdc-morocco/", + "download_url": null, + "language": "en", + "authority_rank": "OFFICIAL_PROGRAM_CHALLENGE_PAGE", + "publication_or_effective_date": "Launch 2026-06-26; page states 2026", + "content_type": "text/html", + "public_accessibility": "PUBLIC_HTML", + "login_requirement": "NONE_FOR_VIEWING", + "redirect_behavior": "DIRECT_CANONICAL_PAGE_OBSERVED", + "expected_byte_format": "HTML", + "translation_status": "OFFICIAL_ENGLISH_CHALLENGE_STATEMENT", + "proposed_corpus_use": "Mission framing evidence only; not normative authority", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "Public page observed through single-page retrieval; no crawl required", + "visible_download_mechanism": false, + "one_direct_get_technically_possible": true, + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "HTML_MUTABLE", + "provenance_without_bypass": true, + "acquisition_risks": ["Mutable HTML", "Terms and redistribution not reviewed"], + "unresolved_issues": ["Full challenge-statement link target not acquired"] + }, + { + "source_id": "CDC-CHALLENGE-001-FR", + "complete_title": "Morocco — Cour des Comptes (CDC) : défi contrôle de la commande publique", + "issuing_authority": "Cour des Comptes / CRC avec Banque mondiale, SECO et Trust Valley", + "canonical_url": "https://govtech.trustvalley.swiss/fr/challenges/cdc-morocco/", + "download_url": null, + "language": "fr", + "authority_rank": "OFFICIAL_PROGRAM_CHALLENGE_PAGE", + "publication_or_effective_date": "Lancement 2026-06-26; page states 2026", + "content_type": "text/html", + "public_accessibility": "PUBLIC_HTML", + "login_requirement": "NONE_FOR_VIEWING", + "redirect_behavior": "DIRECT_CANONICAL_PAGE_OBSERVED", + "expected_byte_format": "HTML", + "translation_status": "OFFICIAL_FRENCH_CHALLENGE_STATEMENT", + "proposed_corpus_use": "French-first mission framing evidence only; not normative authority", + "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY", + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "robots_observation": "Public page observed through single-page retrieval; no crawl required", + "visible_download_mechanism": false, + "one_direct_get_technically_possible": true, + "human_browser_download_available": true, + "automated_crawling_required": false, + "source_bytes_appear_stable": "HTML_MUTABLE", + "provenance_without_bypass": true, + "acquisition_risks": ["Mutable HTML", "Terms and redistribution not reviewed"], + "unresolved_issues": ["Full French challenge attachment, if any, not acquired"] + } + ] +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md new file mode 100644 index 0000000..898e228 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md @@ -0,0 +1,27 @@ +# Morocco Official Source Register v0.1 + +Status: **PREFLIGHT_ONLY_NO_BYTES_ACQUIRED** + +Engineering acquisition classifications only; they are not legal opinions. + +## Sources + +| ID | Title | Authority | Lang | Canonical | Download | Translation | Disposition | +|---|---|---|---|---|---|---|---| +| MAR-NORM-001 | Décret n° 2-22-431 du 15 chaabane 1444 (8 mars 2023) relatif aux marchés publics — édition française 2023 | Chef du Gouvernement; publication hosted by Trésorerie Générale du Royaume | fr | https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/Textes%20legislatifs/Decrets/ | https://www.tgr.gov.ma/wps/wcm/connect/1f3081fc-2d01-41de-8339-9a2c7de0480f/DECRET%2B2-22-431%2BFR.pdf?MOD=AJPERES | ORIGINAL_FRENCH_PUBLICATION | LINK_AND_RECEIPT_ONLY | +| MAR-NORM-001-EN | Decree no. 2-22-431 of 15 Chaabane 1444 (8 March 2023) on public procurement | Trésorerie Générale du Royaume | en | https://www.tgr.gov.ma/wps/portal/Publication/Brochures%20et%20depliants/ | https://www.tgr.gov.ma/wps/wcm/connect/6afbc2bd-5715-4a68-8afa-0cc19d768173/Decree%2BPublic%2Bprocurement_ENG%2Bversion.pdf?ContentCache=NONE&MOD=AJPERES | UNOFFICIAL_TRANSLATION_COMPANION | LINK_AND_RECEIPT_ONLY | +| MAR-IMPL-001 | Arrêtés d’application du décret n° 2-22-431 — compilation officielle, édition 2023 | Ministre délégué chargé du budget; hosted by Trésorerie Générale du Royaume | fr | https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/ | https://www.tgr.gov.ma/wps/wcm/connect/69ff3b86-7c5b-46c7-83fb-bce87a5f0774/ARRETE%2Bdecret%2B2-22-431%2BFR.pdf?MOD=AJPERES | ORIGINAL_FRENCH_PUBLICATION | LINK_AND_RECEIPT_ONLY | +| MAR-CCAG-001 | Décret n° 2-14-394 du 6 chaabane 1437 approuvant le cahier des clauses administratives générales applicables aux marchés de travaux | Chef du Gouvernement; hosted by Trésorerie Générale du Royaume | fr | https://www.tgr.gov.ma/wps/portal/Textes%20reglementaires/Marches%20publics/ | https://www.tgr.gov.ma/wps/wcm/connect/48e2b53e-6fa9-4ee9-a6ae-bbde89b37b6f/2-14-394ccag.pdf?CACHEID=48e2b53e-6fa9-4ee9-a6ae-bbde89b37b6f&MOD=AJPERES | ORIGINAL_FRENCH_PUBLICATION | LINK_AND_RECEIPT_ONLY | +| CDC-CHALLENGE-001 | Morocco — Supreme Audit Institution (Cour des Comptes / CDC): Public Procurement Audit Challenge | Cour des Comptes / CRC with World Bank, SECO and Trust Valley | en | https://govtech.trustvalley.swiss/challenges/cdc-morocco/ | — | OFFICIAL_ENGLISH_CHALLENGE_STATEMENT | LINK_AND_RECEIPT_ONLY | +| CDC-CHALLENGE-001-FR | Morocco — Cour des Comptes (CDC) : défi contrôle de la commande publique | Cour des Comptes / CRC avec Banque mondiale, SECO et Trust Valley | fr | https://govtech.trustvalley.swiss/fr/challenges/cdc-morocco/ | — | OFFICIAL_FRENCH_CHALLENGE_STATEMENT | LINK_AND_RECEIPT_ONLY | + +## Acquisition observations + +| ID | Public | Login | Robots | Direct GET | Human | Crawl | Risks | Unresolved | +|---|---|---|---|---|---|---|---|---| +| MAR-NORM-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; single canonical link provenance can be recorded without bypass | UNTESTED | YES | NO | Rights and redistribution not reviewed; Redirect and exact bytes not frozen | Confirm authoritative publication relationship to Bulletin Officiel before acquisition | +| MAR-NORM-001-EN | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; single canonical link provenance can be recorded without bypass | UNTESTED | YES | NO | Must never be promoted to authoritative; Rights and exact bytes not reviewed | Publication date and translation provenance need receipt evidence | +| MAR-IMPL-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; direct compilation link found through official index/search | UNTESTED | YES | NO | Compilation may contain multiple orders; Exact order inventory requires non-semantic structural indexing after acquisition authorization | Confirm complete order list and current amendment status | +| MAR-CCAG-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; direct official PDF link is visible | UNTESTED | YES | NO | Applicability to a selected mission requires owner/legal confirmation; Predates Decree 2-22-431 | Confirm current applicability and amendment chain without interpreting policy | +| CDC-CHALLENGE-001 | PUBLIC_HTML | NONE_FOR_VIEWING | Public page observed through single-page retrieval; no crawl required | YES | YES | NO | Mutable HTML; Terms and redistribution not reviewed | Full challenge-statement link target not acquired | +| CDC-CHALLENGE-001-FR | PUBLIC_HTML | NONE_FOR_VIEWING | Public page observed through single-page retrieval; no crawl required | YES | YES | NO | Mutable HTML; Terms and redistribution not reviewed | Full French challenge attachment, if any, not acquired | diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json new file mode 100644 index 0000000..dd24673 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json @@ -0,0 +1,60 @@ +{ + "document_id": "OAM-PMP-MISSION-CANDIDATES-v0.1", + "status": "PUBLIC_PORTAL_METADATA_ONLY_NO_DOWNLOADS", + "candidates": [ + { + "candidate_id": "PMP-CASE-001", + "reference": "44/FES/2026", + "public_buyer": "Direction Provinciale du MEN Fès", + "title_fr": "Achat de fournitures de bureau de reprographie et imprimés destinés à la formation continue, aux établissements scolaires et aux services administratifs relevant de la direction provinciale à Fès, en lot unique", + "procedure_type": "Appel d'offres ouvert", + "publication_date": "2026-07-17", + "submission_deadline": "2026-09-04T10:00:00+01:00", + "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549", + "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", + "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", + "download_urls": [], + "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "synthetic": false, + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + }, + { + "candidate_id": "PMP-CASE-002", + "reference": "14/2026", + "public_buyer": "Directeur Régional d'Agriculture de la Région Béni Mellal-Khénifra", + "title_fr": "Achat de matériel et mobilier de bureau au profit du Centre Régional des Jeunes Entrepreneurs Agricoles et Agro-alimentaires relevant de la Direction Régionale de l’Agriculture de Béni Mellal-Khénifra en lot unique", + "procedure_type": "Appel d'offres ouvert simplifié", + "publication_date": "2026-07-22", + "submission_deadline": "2026-09-03T11:00:00+01:00", + "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000", + "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", + "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", + "download_urls": [], + "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "synthetic": false, + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + }, + { + "candidate_id": "PMP-CASE-003", + "reference": "22/2026", + "public_buyer": "Ministère de la Justice", + "title_fr": "Achat de fournitures de classement et d’archivage pour le compte des Juridictions du Royaume en lot unique", + "procedure_type": "Appel d'offres ouvert", + "publication_date": "2026-07-23", + "submission_deadline": "2026-09-03T10:00:00+01:00", + "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408", + "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", + "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", + "download_urls": [], + "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "synthetic": false, + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + } + ] +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md new file mode 100644 index 0000000..0f42550 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md @@ -0,0 +1,42 @@ +# PMP Mission Candidates v0.1 + +Status: **PUBLIC_PORTAL_METADATA_ONLY_NO_DOWNLOADS** + +No procurement is classified as compliant or non-compliant. + +## Candidates + +| ID | Reference | Buyer | Procedure | Deadline | State | Portal | Objects | Disposition | +|---|---|---|---|---|---|---|---|---| +| PMP-CASE-001 | 44/FES/2026 | Direction Provinciale du MEN Fès | Appel d'offres ouvert | 2026-09-04T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-002 | 14/2026 | Directeur Régional d'Agriculture de la Région Béni Mellal-Khénifra | Appel d'offres ouvert simplifié | 2026-09-03T11:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-003 | 22/2026 | Ministère de la Justice | Appel d'offres ouvert | 2026-09-03T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | + +## Missing objects + +### PMP-CASE-001 — 44/FES/2026 + +- DAO/specifications not verified +- Administrative and technical clauses not verified +- Evaluation grid not available +- Commission minutes and award not yet available +- Programme prévisionnel link not established +- Exact artifact URLs and bytes not acquired + +### PMP-CASE-002 — 14/2026 + +- DAO/specifications not verified +- Administrative and technical clauses not verified +- Evaluation grid not available +- Commission minutes and award not yet available +- Programme prévisionnel link not established +- Exact artifact URLs and bytes not acquired + +### PMP-CASE-003 — 22/2026 + +- DAO/specifications not verified +- Administrative and technical clauses not verified +- Evaluation grid not available +- Commission minutes and award not yet available +- Programme prévisionnel link not established +- Exact artifact URLs and bytes not acquired diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json new file mode 100644 index 0000000..4723362 --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json @@ -0,0 +1,15 @@ +{ + "document_id": "OAM-MOROCCO-PREFLIGHT-DECISION-v0.1", + "status": "PROPOSED_PREFLIGHT_DECISION_NOT_SEMANTIC_ADMISSION", + "exact_base": "29daa374b7e5cdc30ca7788310fbabb85f19912b", + "normative_candidates": ["MAR-NORM-001", "MAR-NORM-001-EN", "MAR-IMPL-001", "MAR-CCAG-001"], + "challenge_sources": ["CDC-CHALLENGE-001", "CDC-CHALLENGE-001-FR"], + "pmp_candidate_count": 3, + "preferred_mission": {"mission_id": "MAR-MISSION-001", "candidate_id": "PMP-CASE-001", "reference": "44/FES/2026"}, + "fallback_mission": {"candidate_id": "PMP-CASE-002", "reference": "14/2026"}, + "decision_basis": "Both nominees pass the bounded public-record gate through stable official identity, French notice metadata, buyer, procedure, publication date, deadline and explicit open state. DAO, evaluation and award evidence remain missing and are not inferred. No compliance conclusion is made.", + "no_source_bytes_acquired": true, + "no_semantic_processing": true, + "semantic_implementation_gate": "BLOCKED", + "next_owner_decisions": ["Authorize rights review and exact-byte acquisition receipts", "Confirm works CCAG scope", "Confirm whether visible PV evidence is sufficient before artifact acquisition"] +} diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md new file mode 100644 index 0000000..dabfe1c --- /dev/null +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md @@ -0,0 +1,25 @@ +# Morocco Preflight Decision v0.1 + +Status: **PROPOSED_PREFLIGHT_DECISION_NOT_SEMANTIC_ADMISSION** + +Exact base: `29daa374b7e5cdc30ca7788310fbabb85f19912b` + +PMP candidates: **3** + +Preferred: **MAR-MISSION-001 — 44/FES/2026** + +Fallback: **14/2026** + +Both nominees pass the bounded public-record gate through stable official identity, French notice metadata, buyer, procedure, publication date, deadline and explicit open state. DAO, evaluation and award evidence remain missing and are not inferred. No compliance conclusion is made. + +No source bytes acquired: **YES** + +No semantic processing: **YES** + +Semantic implementation gate: **BLOCKED** + +## Owner decisions still required + +- Authorize rights review and exact-byte acquisition receipts +- Confirm works CCAG scope +- Confirm whether visible PV evidence is sufficient before artifact acquisition diff --git a/tests/contract/test_morocco_preflight.py b/tests/contract/test_morocco_preflight.py new file mode 100644 index 0000000..b7dc422 --- /dev/null +++ b/tests/contract/test_morocco_preflight.py @@ -0,0 +1,176 @@ +"""Contract tests for the Morocco official-source and real-mission preflight.""" + +from __future__ import annotations + +import copy +import json +import subprocess +import sys +from pathlib import Path +from typing import Any, cast +from urllib.parse import urlparse + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +PREFLIGHT = ROOT / "benchmarks/corpus/morocco/preflight-v0.1" +BASE = "29daa374b7e5cdc30ca7788310fbabb85f19912b" +PERMITTED = { + "tgr.gov.ma", + "marchespublics.gov.ma", + "sgg.gov.ma", + "courdescomptes.ma", + "govtech.trustvalley.swiss", + "worldbank.org", +} +LEGAL_CLEARANCE = "LEGALLY_CLEARED" + + +def load(name: str) -> dict[str, Any]: + return cast(dict[str, Any], json.loads((PREFLIGHT / name).read_text(encoding="utf-8"))) + + +def validate( + register: dict[str, Any], candidates: dict[str, Any], scorecard: dict[str, Any] +) -> None: + sources = register["sources"] + ids = [source["source_id"] for source in sources] + assert len(ids) == len(set(ids)) + assert LEGAL_CLEARANCE not in register["engineering_dispositions"] + for source in sources: + assert source["language"] + for key in ("canonical_url", "download_url"): + if source[key] is not None: + host = (urlparse(source[key]).hostname or "").removeprefix("www.") + assert host in PERMITTED + assert source["engineering_disposition"] in register["engineering_dispositions"] + assert source["engineering_disposition"] != LEGAL_CLEARANCE + if source["language"] == "en": + assert source["translation_status"] + if source["translation_status"] == "UNOFFICIAL_TRANSLATION_COMPANION": + assert "NORMATIVE_TEXT" not in source["authority_rank"] + + cases = candidates["candidates"] + assert 3 <= len(cases) <= 7 + assert all(not case["synthetic"] for case in cases) + assert all(case["login_requirement_for_page"] != "REQUIRED" for case in cases) + assert all(case["associated_public_objects"] for case in cases) + assert all(case["state"] for case in cases) + assert all( + case["publication_date"] and "UNRESOLVED" not in case["publication_date"] for case in cases + ) + + dimensions = scorecard["dimensions"] + assert len(dimensions) == 14 + assert len(scorecard["scores"]) == len(cases) + assert ( + sum(score["nomination"] == "PREFERRED_MAR-MISSION-001" for score in scorecard["scores"]) + == 1 + ) + assert sum(score["nomination"] == "FALLBACK" for score in scorecard["scores"]) == 1 + for score in scorecard["scores"]: + assert len(score["values"]) == len(dimensions) + assert all(value in {0, 1, 2} for value in score["values"]) + assert score["total"] == sum(score["values"]) + if score["nomination"] in {"PREFERRED_MAR-MISSION-001", "FALLBACK"}: + assert score["mandatory_minimum"] + + +@pytest.fixture +def bundle() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: + return ( + load("OFFICIAL-SOURCE-REGISTER-v0.1.json"), + load("PMP-MISSION-CANDIDATES-v0.1.json"), + load("MISSION-SELECTION-SCORECARD-v0.1.json"), + ) + + +def test_bundle_contract(bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]]) -> None: + validate(*bundle) + + +def test_deterministic_renderings_are_current() -> None: + result = subprocess.run( + [sys.executable, "tools/render_morocco_preflight.py", "--check"], + cwd=ROOT, + check=False, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stdout + result.stderr + + +def test_no_source_bytes_committed() -> None: + allowed = {".json", ".md"} + assert all(path.suffix in allowed for path in PREFLIGHT.iterdir()) + decision = load("PREFLIGHT-DECISION-v0.1.json") + assert decision["no_source_bytes_acquired"] is True + + +def test_protected_artifacts_unchanged() -> None: + paths = ["STATUS.md", "schemas/draft", "benchmarks/corpus/canada"] + result = subprocess.run(["git", "diff", "--quiet", BASE, "--", *paths], cwd=ROOT, check=False) + assert result.returncode == 0 + + +def test_no_semantic_artifact_created() -> None: + decision = load("PREFLIGHT-DECISION-v0.1.json") + assert decision["no_semantic_processing"] is True + assert decision["semantic_implementation_gate"] == "BLOCKED" + forbidden = ("institutional-ir", "control-envelope", "warrant", "veip") + added = subprocess.run( + ["git", "diff", "--name-only", BASE], cwd=ROOT, check=True, capture_output=True, text=True + ).stdout.lower() + assert not any(token in added for token in forbidden) + + +def test_source_mutations_fail( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]], +) -> None: + mutations = ( + "third_party", + "authoritative_translation", + "duplicate_id", + "missing_language", + "legal_clearance", + ) + for mutation in mutations: + register, candidates, scorecard = copy.deepcopy(bundle) + if mutation == "third_party": + register["sources"][0]["canonical_url"] = "https://example.com/mirror.pdf" + elif mutation == "authoritative_translation": + register["sources"][1]["authority_rank"] = "OFFICIAL_NORMATIVE_TEXT" + elif mutation == "duplicate_id": + register["sources"][1]["source_id"] = register["sources"][0]["source_id"] + elif mutation == "missing_language": + register["sources"][0]["language"] = "" + else: + register["sources"][0]["engineering_disposition"] = LEGAL_CLEARANCE + with pytest.raises(AssertionError): + validate(register, candidates, scorecard) + + +def test_mission_mutations_fail( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]], +) -> None: + mutations = ( + "missing_artifact", + "inflated_score", + "synthetic", + "login_required", + "mandatory_bypass", + ) + for mutation in mutations: + register, candidates, scorecard = copy.deepcopy(bundle) + if mutation == "missing_artifact": + candidates["candidates"][0]["associated_public_objects"] = [] + elif mutation == "inflated_score": + scorecard["scores"][0]["total"] += 1 + elif mutation == "synthetic": + candidates["candidates"][0]["synthetic"] = True + elif mutation == "login_required": + candidates["candidates"][0]["login_requirement_for_page"] = "REQUIRED" + else: + scorecard["scores"][0]["mandatory_minimum"] = False + with pytest.raises(AssertionError): + validate(register, candidates, scorecard) diff --git a/tools/render_morocco_preflight.py b/tools/render_morocco_preflight.py new file mode 100644 index 0000000..a25fe36 --- /dev/null +++ b/tools/render_morocco_preflight.py @@ -0,0 +1,311 @@ +"""Render Morocco preflight canonical JSON files to deterministic Markdown.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +PREFLIGHT = ROOT / "benchmarks/corpus/morocco/preflight-v0.1" +STEMS = ( + "OFFICIAL-SOURCE-REGISTER-v0.1", + "NORMATIVE-SOURCE-MAP-v0.1", + "PMP-MISSION-CANDIDATES-v0.1", + "MISSION-SELECTION-SCORECARD-v0.1", + "ACQUISITION-CONSTRAINTS-v0.1", + "PREFLIGHT-DECISION-v0.1", +) + + +def _load(stem: str) -> dict[str, Any]: + return json.loads((PREFLIGHT / f"{stem}.json").read_text(encoding="utf-8")) + + +def _cell(value: object) -> str: + if value is None: + return "—" + if isinstance(value, list): + return "; ".join(str(item) for item in value) or "—" + if isinstance(value, bool): + return "YES" if value else "NO" + return str(value).replace("|", "\\|").replace("\n", " ") + + +def _table(headers: list[str], rows: list[list[object]]) -> list[str]: + lines = ["| " + " | ".join(headers) + " |", "|" + "|".join(["---"] * len(headers)) + "|"] + lines.extend("| " + " | ".join(_cell(value) for value in row) + " |" for row in rows) + return lines + + +def render_source_register(data: dict[str, Any]) -> str: + sources = data["sources"] + lines = [ + "# Morocco Official Source Register v0.1", + "", + f"Status: **{data['status']}**", + "", + "Engineering acquisition classifications only; they are not legal opinions.", + "", + "## Sources", + "", + ] + lines += _table( + ["ID", "Title", "Authority", "Lang", "Canonical", "Download", "Translation", "Disposition"], + [ + [ + source["source_id"], + source["complete_title"], + source["issuing_authority"], + source["language"], + source["canonical_url"], + source["download_url"], + source["translation_status"], + source["engineering_disposition"], + ] + for source in sources + ], + ) + lines += ["", "## Acquisition observations", ""] + lines += _table( + ["ID", "Public", "Login", "Robots", "Direct GET", "Human", "Crawl", "Risks", "Unresolved"], + [ + [ + source["source_id"], + source["public_accessibility"], + source["login_requirement"], + source["robots_observation"], + source["one_direct_get_technically_possible"], + source["human_browser_download_available"], + source["automated_crawling_required"], + source["acquisition_risks"], + source["unresolved_issues"], + ] + for source in sources + ], + ) + return "\n".join(lines) + "\n" + + +def render_normative_map(data: dict[str, Any]) -> str: + lines = [ + "# Morocco Normative Source Map v0.1", + "", + f"Status: **{data['status']}**", + "", + "No legal meaning or applicability conclusion is expressed.", + "", + "## Map", + "", + ] + lines += _table( + ["ID", "Role", "Authoritative", "Relationship"], + [ + [ + entry["source_id"], + entry["role"], + entry["authoritative_for_semantics"], + entry["relationship"], + ] + for entry in data["entries"] + ], + ) + lines += ["", "## Unresolved", ""] + [f"- {item}" for item in data["unresolved"]] + return "\n".join(lines) + "\n" + + +def render_candidates(data: dict[str, Any]) -> str: + lines = [ + "# PMP Mission Candidates v0.1", + "", + f"Status: **{data['status']}**", + "", + "No procurement is classified as compliant or non-compliant.", + "", + "## Candidates", + "", + ] + lines += _table( + [ + "ID", + "Reference", + "Buyer", + "Procedure", + "Deadline", + "State", + "Portal", + "Objects", + "Disposition", + ], + [ + [ + case["candidate_id"], + case["reference"], + case["public_buyer"], + case["procedure_type"], + case["submission_deadline"], + case["state"], + case["official_portal_url"], + case["associated_public_objects"], + case["engineering_disposition"], + ] + for case in data["candidates"] + ], + ) + lines += ["", "## Missing objects", ""] + for case in data["candidates"]: + lines += ( + [f"### {case['candidate_id']} — {case['reference']}", ""] + + [f"- {item}" for item in case["missing_objects"]] + + [""] + ) + return "\n".join(lines).rstrip() + "\n" + + +def render_scorecard(data: dict[str, Any]) -> str: + dimensions = data["dimensions"] + lines = [ + "# Morocco Mission Selection Scorecard v0.1", + "", + "Scores: 0 absent; 1 partial/uncertain; 2 present and official.", + "", + f"Rule: {data['selection_rule']}", + "", + "## Complete scorecard", + "", + ] + lines += _table( + ["Candidate", *dimensions, "Total", "Mandatory", "Nomination"], + [ + [ + score["candidate_id"], + *score["values"], + score["total"], + score["mandatory_minimum"], + score["nomination"], + ] + for score in data["scores"] + ], + ) + lines += [ + "", + f"Preferred: **{data['preferred']['mission_id']} → {data['preferred']['candidate_id']}**", + "", + f"Fallback: **{data['fallback']['candidate_id']}**", + "", + ] + return "\n".join(lines) + + +def render_constraints(data: dict[str, Any]) -> str: + lines = [ + "# Morocco Acquisition Constraints v0.1", + "", + f"Status: **{data['status']}**", + "", + f"No source bytes acquired: **{_cell(data['no_source_bytes_acquired'])}**", + "", + "## Constraints", + "", + ] + lines += _table( + [ + "Scope", + "Disposition", + "Robots", + "Authentication", + "Visible", + "Direct GET", + "Human", + "Crawl", + "Stability", + "Blockers", + ], + [ + [ + item["scope"], + item["disposition"], + item["robots_observation"], + item["authentication"], + item["visible_download"], + item["direct_get"], + item["human_download"], + item["crawl_required"], + item["stability"], + item["blockers"], + ] + for item in data["constraints"] + ], + ) + lines += ["", "## Prohibited", ""] + [f"- {item}" for item in data["prohibited"]] + return "\n".join(lines) + "\n" + + +def render_decision(data: dict[str, Any]) -> str: + preferred = data["preferred_mission"] + return "\n".join( + [ + "# Morocco Preflight Decision v0.1", + "", + f"Status: **{data['status']}**", + "", + f"Exact base: `{data['exact_base']}`", + "", + f"PMP candidates: **{data['pmp_candidate_count']}**", + "", + f"Preferred: **{preferred['mission_id']} — {preferred['reference']}**", + "", + f"Fallback: **{data['fallback_mission']['reference']}**", + "", + data["decision_basis"], + "", + f"No source bytes acquired: **{_cell(data['no_source_bytes_acquired'])}**", + "", + f"No semantic processing: **{_cell(data['no_semantic_processing'])}**", + "", + f"Semantic implementation gate: **{data['semantic_implementation_gate']}**", + "", + "## Owner decisions still required", + "", + *[f"- {item}" for item in data["next_owner_decisions"]], + "", + ] + ) + + +RENDERERS = { + STEMS[0]: render_source_register, + STEMS[1]: render_normative_map, + STEMS[2]: render_candidates, + STEMS[3]: render_scorecard, + STEMS[4]: render_constraints, + STEMS[5]: render_decision, +} + + +def render_all(*, check: bool) -> bool: + clean = True + for stem in STEMS: + rendered = RENDERERS[stem](_load(stem)) + path = PREFLIGHT / f"{stem}.md" + if check: + clean = clean and path.exists() and path.read_text(encoding="utf-8") == rendered + else: + path.write_text(rendered, encoding="utf-8") + return clean + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + if not render_all(check=args.check): + print("Morocco preflight Markdown is stale") + return 1 + print("Morocco preflight Markdown is current") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 7a7f044a7580bf0ef7f58ca30b8e327a9d87ce82 Mon Sep 17 00:00:00 2001 From: //AM Date: Fri, 31 Jul 2026 17:07:13 -0400 Subject: [PATCH 2/3] research: correct Morocco mission evidence gate --- .../MISSION-SELECTION-SCORECARD-v0.1.json | 78 +++- .../MISSION-SELECTION-SCORECARD-v0.1.md | 16 +- .../NORMATIVE-SOURCE-MAP-v0.1.json | 59 ++- .../NORMATIVE-SOURCE-MAP-v0.1.md | 16 +- .../OFFICIAL-SOURCE-REGISTER-v0.1.json | 57 ++- .../OFFICIAL-SOURCE-REGISTER-v0.1.md | 12 +- .../PMP-MISSION-CANDIDATES-v0.1.json | 341 ++++++++++++++++- .../PMP-MISSION-CANDIDATES-v0.1.md | 103 +++++- .../PREFLIGHT-DECISION-v0.1.json | 41 ++- .../preflight-v0.1/PREFLIGHT-DECISION-v0.1.md | 16 +- tests/contract/test_morocco_preflight.py | 343 ++++++++++++++++-- tools/render_morocco_preflight.py | 120 +++++- 12 files changed, 1086 insertions(+), 116 deletions(-) diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json index 9d62c9a..a4d977b 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json @@ -1,13 +1,75 @@ { "document_id": "OAM-MISSION-SELECTION-SCORECARD-v0.1", - "scale": {"0": "absent", "1": "partial_or_uncertain", "2": "present_and_official"}, - "dimensions": ["stable_official_identity", "french_source_availability", "tender_notice_availability", "dao_specifications_tor", "administrative_or_technical_clauses", "amendment_or_addendum_evidence", "evaluation_criteria_grid", "commission_minute_or_extract", "award_or_result", "programme_previsionnel_linkage", "buyer_procedure_value_date_completeness", "exact_byte_downloadable_artifacts", "no_login_captcha_access_control", "tender_evaluation_award_suitability"], + "scale": { + "0": "absent", + "1": "partial_or_uncertain", + "2": "present_and_official" + }, + "dimensions": [ + "stable_official_identity", + "french_source_availability", + "tender_notice_availability", + "dao_specifications_tor", + "administrative_or_technical_clauses", + "amendment_or_addendum_evidence", + "evaluation_criteria_grid", + "commission_minute_or_extract", + "award_or_result", + "programme_previsionnel_linkage", + "buyer_procedure_value_date_completeness", + "exact_byte_downloadable_artifacts", + "no_login_captcha_access_control", + "tender_evaluation_award_suitability" + ], "scores": [ - {"candidate_id": "PMP-CASE-001", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "PREFERRED_MAR-MISSION-001"}, - {"candidate_id": "PMP-CASE-002", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "FALLBACK"}, - {"candidate_id": "PMP-CASE-003", "values": [2,2,2,0,0,0,0,0,2,0,1,0,2,1], "total": 12, "mandatory_minimum": true, "nomination": "NOT_SELECTED"} + { + "candidate_id": "PMP-CASE-001", + "values": [2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], + "total": 11, + "mandatory_gate_result": "PASS", + "gate_evidence_reference": "PMP-CASE-001#mandatory_gate", + "nomination": "NOT_SELECTED" + }, + { + "candidate_id": "PMP-CASE-002", + "values": [2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], + "total": 11, + "mandatory_gate_result": "PASS", + "gate_evidence_reference": "PMP-CASE-002#mandatory_gate", + "nomination": "NOT_SELECTED" + }, + { + "candidate_id": "PMP-CASE-003", + "values": [2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], + "total": 11, + "mandatory_gate_result": "PASS", + "gate_evidence_reference": "PMP-CASE-003#mandatory_gate", + "nomination": "NOT_SELECTED" + }, + { + "candidate_id": "PMP-CASE-004", + "values": [2, 2, 2, 1, 0, 0, 0, 2, 2, 0, 1, 1, 1, 2], + "total": 16, + "mandatory_gate_result": "PASS", + "gate_evidence_reference": "PMP-CASE-004#mandatory_gate", + "nomination": "PREFERRED_MAR-MISSION-001" + }, + { + "candidate_id": "PMP-CASE-005", + "values": [2, 2, 2, 1, 0, 0, 0, 2, 2, 0, 1, 1, 1, 2], + "total": 16, + "mandatory_gate_result": "PASS", + "gate_evidence_reference": "PMP-CASE-005#mandatory_gate", + "nomination": "FALLBACK" + } ], - "selection_rule": "Mandatory criteria gate first; these are open cases with official structured notices but no acquired DAO, evaluation or award objects; aggregate score cannot override missing evidence", - "preferred": {"mission_id": "MAR-MISSION-001", "candidate_id": "PMP-CASE-001"}, - "fallback": {"candidate_id": "PMP-CASE-002"} + "selection_rule": "Derive the eight mandatory criteria from candidate evidence before scoring. Aggregate score never overrides the mandatory gate. Open state is recorded separately and never counted as award evidence.", + "selection_outcome": "ADMISSIBLE_CASES_FOUND", + "preferred": { + "mission_id": "MAR-MISSION-001", + "candidate_id": "PMP-CASE-004" + }, + "fallback": { + "candidate_id": "PMP-CASE-005" + } } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md index 0b4b17b..20e9a3c 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md @@ -2,16 +2,20 @@ Scores: 0 absent; 1 partial/uncertain; 2 present and official. -Rule: Mandatory criteria gate first; these are open cases with official structured notices but no acquired DAO, evaluation or award objects; aggregate score cannot override missing evidence +Rule: Derive the eight mandatory criteria from candidate evidence before scoring. Aggregate score never overrides the mandatory gate. Open state is recorded separately and never counted as award evidence. ## Complete scorecard | Candidate | stable_official_identity | french_source_availability | tender_notice_availability | dao_specifications_tor | administrative_or_technical_clauses | amendment_or_addendum_evidence | evaluation_criteria_grid | commission_minute_or_extract | award_or_result | programme_previsionnel_linkage | buyer_procedure_value_date_completeness | exact_byte_downloadable_artifacts | no_login_captcha_access_control | tender_evaluation_award_suitability | Total | Mandatory | Nomination | |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| -| PMP-CASE-001 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | PREFERRED_MAR-MISSION-001 | -| PMP-CASE-002 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | FALLBACK | -| PMP-CASE-003 | 2 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 1 | 0 | 2 | 1 | 12 | YES | NOT_SELECTED | +| PMP-CASE-001 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 11 | PASS | NOT_SELECTED | +| PMP-CASE-002 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 11 | PASS | NOT_SELECTED | +| PMP-CASE-003 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 11 | PASS | NOT_SELECTED | +| PMP-CASE-004 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 2 | 2 | 0 | 1 | 1 | 1 | 2 | 16 | PASS | PREFERRED_MAR-MISSION-001 | +| PMP-CASE-005 | 2 | 2 | 2 | 1 | 0 | 0 | 0 | 2 | 2 | 0 | 1 | 1 | 1 | 2 | 16 | PASS | FALLBACK | -Preferred: **MAR-MISSION-001 → PMP-CASE-001** +Outcome: **ADMISSIBLE_CASES_FOUND** -Fallback: **PMP-CASE-002** +Preferred: **MAR-MISSION-001 → PMP-CASE-004** + +Fallback: **PMP-CASE-005** diff --git a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json index f62ccd0..2e14b40 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.json @@ -3,12 +3,57 @@ "status": "PREFLIGHT_NO_LEGAL_INTERPRETATION", "french_first": true, "entries": [ - {"source_id": "MAR-NORM-001", "role": "PRIMARY_FRENCH_NORMATIVE_CANDIDATE", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Decree 2-22-431"}, - {"source_id": "MAR-NORM-001-EN", "role": "UNOFFICIAL_TRANSLATION_COMPANION", "authoritative_for_semantics": false, "relationship": "Navigation companion to MAR-NORM-001"}, - {"source_id": "MAR-IMPL-001", "role": "IMPLEMENTING_ORDERS_COMPILATION", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Orders expressly cite Decree 2-22-431"}, - {"source_id": "MAR-CCAG-001", "role": "WORKS_SECTOR_CANDIDATE", "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", "relationship": "Potentially relevant only to a works case; no applicability conclusion"}, - {"source_id": "CDC-CHALLENGE-001", "role": "MISSION_FRAMING_EN", "authoritative_for_semantics": false, "relationship": "Challenge statement, not normative text"}, - {"source_id": "CDC-CHALLENGE-001-FR", "role": "MISSION_FRAMING_FR", "authoritative_for_semantics": false, "relationship": "French challenge page, not normative text"} + { + "source_id": "MAR-NORM-001", + "role": "PRIMARY_FRENCH_NORMATIVE_CANDIDATE", + "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", + "relationship": "Decree 2-22-431" + }, + { + "source_id": "MAR-NORM-001-EN", + "role": "UNOFFICIAL_TRANSLATION_COMPANION", + "authoritative_for_semantics": false, + "relationship": "Navigation companion to MAR-NORM-001" + }, + { + "source_id": "MAR-IMPL-001", + "role": "IMPLEMENTING_ORDERS_COMPILATION", + "authoritative_for_semantics": "OWNER_AND_LEGAL_CONFIRMATION_REQUIRED", + "relationship": "Orders expressly cite Decree 2-22-431" + }, + { + "source_id": "MAR-CCAG-001", + "role": "DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION", + "authoritative_for_semantics": false, + "relationship": "Works CCAG excluded from the proposed normative corpus because the selected mission is a goods procurement" + }, + { + "source_id": "CDC-CHALLENGE-001", + "role": "MISSION_FRAMING_EN", + "authoritative_for_semantics": false, + "relationship": "Challenge statement, not normative text" + }, + { + "source_id": "CDC-CHALLENGE-001-FR", + "role": "MISSION_FRAMING_FR", + "authoritative_for_semantics": false, + "relationship": "French challenge page, not normative text" + } ], - "unresolved": ["Bulletin Officiel chain and amendments", "Current CCAG applicability", "Complete implementing-order inventory"] + "proposed_normative_corpus": [ + "MAR-NORM-001", + "MAR-NORM-001-EN", + "MAR-IMPL-001" + ], + "excluded_from_proposed_normative_corpus": [ + { + "source_id": "MAR-CCAG-001", + "reason": "Procurement-category mismatch: works instrument versus selected goods mission" + } + ], + "unresolved": [ + "Bulletin Officiel chain and amendments", + "Complete implementing-order inventory", + "Applicable official goods instrument, if any, was not identified without policy interpretation" + ] } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md index d90d369..91be056 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/NORMATIVE-SOURCE-MAP-v0.1.md @@ -11,12 +11,24 @@ No legal meaning or applicability conclusion is expressed. | MAR-NORM-001 | PRIMARY_FRENCH_NORMATIVE_CANDIDATE | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Decree 2-22-431 | | MAR-NORM-001-EN | UNOFFICIAL_TRANSLATION_COMPANION | NO | Navigation companion to MAR-NORM-001 | | MAR-IMPL-001 | IMPLEMENTING_ORDERS_COMPILATION | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Orders expressly cite Decree 2-22-431 | -| MAR-CCAG-001 | WORKS_SECTOR_CANDIDATE | OWNER_AND_LEGAL_CONFIRMATION_REQUIRED | Potentially relevant only to a works case; no applicability conclusion | +| MAR-CCAG-001 | DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION | NO | Works CCAG excluded from the proposed normative corpus because the selected mission is a goods procurement | | CDC-CHALLENGE-001 | MISSION_FRAMING_EN | NO | Challenge statement, not normative text | | CDC-CHALLENGE-001-FR | MISSION_FRAMING_FR | NO | French challenge page, not normative text | +## Proposed normative corpus + +- MAR-NORM-001 +- MAR-NORM-001-EN +- MAR-IMPL-001 + +## Excluded discovery candidates + +| ID | Reason | +|---|---| +| MAR-CCAG-001 | Procurement-category mismatch: works instrument versus selected goods mission | + ## Unresolved - Bulletin Officiel chain and amendments -- Current CCAG applicability - Complete implementing-order inventory +- Applicable official goods instrument, if any, was not identified without policy interpretation diff --git a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json index a0517bd..51d9cb1 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.json @@ -1,7 +1,7 @@ { "document_id": "OAM-MOROCCO-OFFICIAL-SOURCE-REGISTER-v0.1", "status": "PREFLIGHT_ONLY_NO_BYTES_ACQUIRED", - "observed_utc": "2026-07-31T00:00:00Z", + "observed_utc": "2026-07-31T20:49:46Z", "permitted_domains": ["tgr.gov.ma", "marchespublics.gov.ma", "sgg.gov.ma", "courdescomptes.ma", "govtech.trustvalley.swiss", "worldbank.org"], "engineering_dispositions": ["PUBLIC_REPO_BYTES_CANDIDATE", "LOCAL_REAL_BYTES_HASHED", "LINK_AND_RECEIPT_ONLY", "BLOCKED_TECHNICALLY", "BLOCKED_ACCESS_CONTROL", "UNRESOLVED"], "sources": [ @@ -104,8 +104,8 @@ "redirect_behavior": "NOT_BYTE_TESTED_IN_PREFLIGHT", "expected_byte_format": "PDF", "translation_status": "ORIGINAL_FRENCH_PUBLICATION", - "proposed_corpus_use": "Sectoral candidate only for a works procurement mission; relevance is metadata-level and not a legal applicability conclusion", - "proposed_storage_disposition": "LINK_AND_RECEIPT_ONLY_PENDING_OWNER_SCOPE_AND_RIGHTS", + "proposed_corpus_use": "DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION; works CCAG excluded from the proposed normative corpus for the selected goods mission", + "proposed_storage_disposition": "LINK_ONLY_NOT_IN_PROPOSED_NORMATIVE_CORPUS", "engineering_disposition": "LINK_AND_RECEIPT_ONLY", "robots_observation": "No crawler execution; direct official PDF link is visible", "visible_download_mechanism": true, @@ -114,8 +114,8 @@ "automated_crawling_required": false, "source_bytes_appear_stable": "LIKELY_UUID_PATH_NOT_VERIFIED", "provenance_without_bypass": true, - "acquisition_risks": ["Applicability to a selected mission requires owner/legal confirmation", "Predates Decree 2-22-431"], - "unresolved_issues": ["Confirm current applicability and amendment chain without interpreting policy"] + "acquisition_risks": ["Predates Decree 2-22-431", "Not applicable to the selected goods mission by procurement category"], + "unresolved_issues": ["Current status outside the selected goods mission was not assessed"] }, { "source_id": "CDC-CHALLENGE-001", @@ -173,5 +173,52 @@ "acquisition_risks": ["Mutable HTML", "Terms and redistribution not reviewed"], "unresolved_issues": ["Full French challenge attachment, if any, not acquired"] } + ], + "mission_evidence_observations": [ + { + "candidate_id": "PMP-CASE-001", + "consultation_record_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549", + "verified_tender_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1024549&orgAcronyme=p8x", + "verified_commission_artifact_url": null, + "verified_result_artifact_url": null, + "observed_utc": "2026-07-31T20:49:46Z", + "bytes_acquired": false + }, + { + "candidate_id": "PMP-CASE-002", + "consultation_record_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000", + "verified_tender_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1017000&orgAcronyme=q1s", + "verified_commission_artifact_url": null, + "verified_result_artifact_url": null, + "observed_utc": "2026-07-31T20:49:46Z", + "bytes_acquired": false + }, + { + "candidate_id": "PMP-CASE-003", + "consultation_record_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408", + "verified_tender_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1012408&orgAcronyme=t5y", + "verified_commission_artifact_url": null, + "verified_result_artifact_url": null, + "observed_utc": "2026-07-31T20:49:46Z", + "bytes_acquired": false + }, + { + "candidate_id": "PMP-CASE-004", + "consultation_record_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u", + "verified_tender_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1015612&orgAcronyme=g0u", + "verified_commission_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1015612&orgAcronyme=g0u", + "verified_result_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u", + "observed_utc": "2026-07-31T20:49:46Z", + "bytes_acquired": false + }, + { + "candidate_id": "PMP-CASE-005", + "consultation_record_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u", + "verified_tender_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1010827&orgAcronyme=g0u", + "verified_commission_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1010827&orgAcronyme=g0u", + "verified_result_artifact_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u", + "observed_utc": "2026-07-31T20:49:46Z", + "bytes_acquired": false + } ] } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md index 898e228..5fc0604 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/OFFICIAL-SOURCE-REGISTER-v0.1.md @@ -22,6 +22,16 @@ Engineering acquisition classifications only; they are not legal opinions. | MAR-NORM-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; single canonical link provenance can be recorded without bypass | UNTESTED | YES | NO | Rights and redistribution not reviewed; Redirect and exact bytes not frozen | Confirm authoritative publication relationship to Bulletin Officiel before acquisition | | MAR-NORM-001-EN | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; single canonical link provenance can be recorded without bypass | UNTESTED | YES | NO | Must never be promoted to authoritative; Rights and exact bytes not reviewed | Publication date and translation provenance need receipt evidence | | MAR-IMPL-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; direct compilation link found through official index/search | UNTESTED | YES | NO | Compilation may contain multiple orders; Exact order inventory requires non-semantic structural indexing after acquisition authorization | Confirm complete order list and current amendment status | -| MAR-CCAG-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; direct official PDF link is visible | UNTESTED | YES | NO | Applicability to a selected mission requires owner/legal confirmation; Predates Decree 2-22-431 | Confirm current applicability and amendment chain without interpreting policy | +| MAR-CCAG-001 | PUBLIC_METADATA_AND_VISIBLE_PDF_LINK | NONE_OBSERVED | No crawler execution; direct official PDF link is visible | UNTESTED | YES | NO | Predates Decree 2-22-431; Not applicable to the selected goods mission by procurement category | Current status outside the selected goods mission was not assessed | | CDC-CHALLENGE-001 | PUBLIC_HTML | NONE_FOR_VIEWING | Public page observed through single-page retrieval; no crawl required | YES | YES | NO | Mutable HTML; Terms and redistribution not reviewed | Full challenge-statement link target not acquired | | CDC-CHALLENGE-001-FR | PUBLIC_HTML | NONE_FOR_VIEWING | Public page observed through single-page retrieval; no crawl required | YES | YES | NO | Mutable HTML; Terms and redistribution not reviewed | Full French challenge attachment, if any, not acquired | + +## Mission evidence observations + +| Candidate | Consultation | Tender artifact | Commission artifact | Result artifact | Observed | Bytes | +|---|---|---|---|---|---|---| +| PMP-CASE-001 | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1024549&orgAcronyme=p8x | — | — | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-002 | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1017000&orgAcronyme=q1s | — | — | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-003 | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1012408&orgAcronyme=t5y | — | — | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-004 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1015612&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1015612&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-005 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1010827&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1010827&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u | 2026-07-31T20:49:46Z | NO | diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json index dd24673..f38fd02 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json @@ -1,60 +1,369 @@ { "document_id": "OAM-PMP-MISSION-CANDIDATES-v0.1", - "status": "PUBLIC_PORTAL_METADATA_ONLY_NO_DOWNLOADS", + "status": "PUBLIC_PORTAL_EVIDENCE_ONLY_NO_DOWNLOADS", + "observed_utc": "2026-07-31T20:49:46Z", + "observation_method": "READ_ONLY_PUBLIC_PORTAL_INSPECTION_NO_ARTIFACT_DOWNLOAD", "candidates": [ { "candidate_id": "PMP-CASE-001", "reference": "44/FES/2026", "public_buyer": "Direction Provinciale du MEN Fès", "title_fr": "Achat de fournitures de bureau de reprographie et imprimés destinés à la formation continue, aux établissements scolaires et aux services administratifs relevant de la direction provinciale à Fès, en lot unique", + "procurement_category": "GOODS_SUPPLIES", "procedure_type": "Appel d'offres ouvert", "publication_date": "2026-07-17", "submission_deadline": "2026-09-04T10:00:00+01:00", "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549", - "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "structured_consultation_record": { + "evidence_id": "PMP-CASE-001-NOTICE", + "language": "fr", + "record_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549" + }, + "verified_tender_document_artifacts": [ + { + "artifact_id": "PMP-CASE-001-DCE-001", + "artifact_type": "DOSSIER_DE_CONSULTATION_DCE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1024549&orgAcronyme=p8x", + "source_candidate_id": "PMP-CASE-001", + "public_accessibility_state": "PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "form_submission_required_for_bytes": true, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "portal_displayed_size": "375.42 KiB", + "bytes_acquired": false + } + ], + "verified_commission_or_evaluation_artifacts": [], + "verified_award_or_result_artifacts": [], "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", - "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", - "download_urls": [], - "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "explicit_official_current_open_state": true, "synthetic": false, "engineering_disposition": "LINK_AND_RECEIPT_ONLY", - "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + "mandatory_gate": { + "eligible": true, + "criteria": { + "stable_official_procurement_reference": {"passed": true, "evidence_refs": ["reference", "official_portal_url"]}, + "official_french_notice_or_consultation_record": {"passed": true, "evidence_refs": ["PMP-CASE-001-NOTICE"]}, + "buyer_and_procedure_metadata": {"passed": true, "evidence_refs": ["public_buyer", "procedure_type"]}, + "verified_real_tender_document_artifact": {"passed": true, "evidence_refs": ["PMP-CASE-001-DCE-001"]}, + "verified_award_or_result_or_explicit_open_state": {"passed": true, "evidence_refs": ["explicit_official_current_open_state"]}, + "no_login_captcha_or_access_control_bypass": {"passed": true, "evidence_refs": ["PMP-CASE-001-DCE-001"]}, + "no_synthetic_object": {"passed": true, "evidence_refs": ["synthetic"]}, + "all_urls_on_permitted_official_domain": {"passed": true, "evidence_refs": ["official_portal_url", "PMP-CASE-001-DCE-001"]} + } + }, + "missing_objects": [ + "DCE contents and specifications not inspected or acquired", + "Administrative and technical clauses not verified", + "Evaluation grid not available", + "Commission minutes and award not available for this open case", + "Programme prévisionnel link not established", + "Exact artifact bytes not acquired" + ] }, { "candidate_id": "PMP-CASE-002", "reference": "14/2026", "public_buyer": "Directeur Régional d'Agriculture de la Région Béni Mellal-Khénifra", "title_fr": "Achat de matériel et mobilier de bureau au profit du Centre Régional des Jeunes Entrepreneurs Agricoles et Agro-alimentaires relevant de la Direction Régionale de l’Agriculture de Béni Mellal-Khénifra en lot unique", + "procurement_category": "GOODS_SUPPLIES", "procedure_type": "Appel d'offres ouvert simplifié", "publication_date": "2026-07-22", "submission_deadline": "2026-09-03T11:00:00+01:00", "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000", - "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "structured_consultation_record": { + "evidence_id": "PMP-CASE-002-NOTICE", + "language": "fr", + "record_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000" + }, + "verified_tender_document_artifacts": [ + { + "artifact_id": "PMP-CASE-002-DCE-001", + "artifact_type": "DOSSIER_DE_CONSULTATION_DCE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1017000&orgAcronyme=q1s", + "source_candidate_id": "PMP-CASE-002", + "public_accessibility_state": "PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "form_submission_required_for_bytes": true, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "portal_displayed_size": "725.5 KiB", + "bytes_acquired": false + } + ], + "verified_commission_or_evaluation_artifacts": [], + "verified_award_or_result_artifacts": [], "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", - "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", - "download_urls": [], - "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "explicit_official_current_open_state": true, "synthetic": false, "engineering_disposition": "LINK_AND_RECEIPT_ONLY", - "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + "mandatory_gate": { + "eligible": true, + "criteria": { + "stable_official_procurement_reference": {"passed": true, "evidence_refs": ["reference", "official_portal_url"]}, + "official_french_notice_or_consultation_record": {"passed": true, "evidence_refs": ["PMP-CASE-002-NOTICE"]}, + "buyer_and_procedure_metadata": {"passed": true, "evidence_refs": ["public_buyer", "procedure_type"]}, + "verified_real_tender_document_artifact": {"passed": true, "evidence_refs": ["PMP-CASE-002-DCE-001"]}, + "verified_award_or_result_or_explicit_open_state": {"passed": true, "evidence_refs": ["explicit_official_current_open_state"]}, + "no_login_captcha_or_access_control_bypass": {"passed": true, "evidence_refs": ["PMP-CASE-002-DCE-001"]}, + "no_synthetic_object": {"passed": true, "evidence_refs": ["synthetic"]}, + "all_urls_on_permitted_official_domain": {"passed": true, "evidence_refs": ["official_portal_url", "PMP-CASE-002-DCE-001"]} + } + }, + "missing_objects": [ + "DCE contents and specifications not inspected or acquired", + "Administrative and technical clauses not verified", + "Evaluation grid not available", + "Commission minutes and award not available for this open case", + "Programme prévisionnel link not established", + "Exact artifact bytes not acquired" + ] }, { "candidate_id": "PMP-CASE-003", "reference": "22/2026", "public_buyer": "Ministère de la Justice", "title_fr": "Achat de fournitures de classement et d’archivage pour le compte des Juridictions du Royaume en lot unique", + "procurement_category": "GOODS_SUPPLIES", "procedure_type": "Appel d'offres ouvert", "publication_date": "2026-07-23", "submission_deadline": "2026-09-03T10:00:00+01:00", "official_portal_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408", - "associated_public_objects": ["Official structured consultation notice in the public portal search result"], + "structured_consultation_record": { + "evidence_id": "PMP-CASE-003-NOTICE", + "language": "fr", + "record_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408" + }, + "verified_tender_document_artifacts": [ + { + "artifact_id": "PMP-CASE-003-DCE-001", + "artifact_type": "DOSSIER_DE_CONSULTATION_DCE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1012408&orgAcronyme=t5y", + "source_candidate_id": "PMP-CASE-003", + "public_accessibility_state": "PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "form_submission_required_for_bytes": true, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "portal_displayed_size": "9.41 MiB", + "bytes_acquired": false + } + ], + "verified_commission_or_evaluation_artifacts": [], + "verified_award_or_result_artifacts": [], "state": "OPEN_DEADLINE_FUTURE_AT_OBSERVATION", - "login_requirement_for_page": "NONE_FOR_PUBLIC_SEARCH_RECORD", - "download_urls": [], - "download_url_note": "No artifact action invoked; DAO availability remains unresolved", + "explicit_official_current_open_state": true, "synthetic": false, "engineering_disposition": "LINK_AND_RECEIPT_ONLY", - "missing_objects": ["DAO/specifications not verified", "Administrative and technical clauses not verified", "Evaluation grid not available", "Commission minutes and award not yet available", "Programme prévisionnel link not established", "Exact artifact URLs and bytes not acquired"] + "mandatory_gate": { + "eligible": true, + "criteria": { + "stable_official_procurement_reference": {"passed": true, "evidence_refs": ["reference", "official_portal_url"]}, + "official_french_notice_or_consultation_record": {"passed": true, "evidence_refs": ["PMP-CASE-003-NOTICE"]}, + "buyer_and_procedure_metadata": {"passed": true, "evidence_refs": ["public_buyer", "procedure_type"]}, + "verified_real_tender_document_artifact": {"passed": true, "evidence_refs": ["PMP-CASE-003-DCE-001"]}, + "verified_award_or_result_or_explicit_open_state": {"passed": true, "evidence_refs": ["explicit_official_current_open_state"]}, + "no_login_captcha_or_access_control_bypass": {"passed": true, "evidence_refs": ["PMP-CASE-003-DCE-001"]}, + "no_synthetic_object": {"passed": true, "evidence_refs": ["synthetic"]}, + "all_urls_on_permitted_official_domain": {"passed": true, "evidence_refs": ["official_portal_url", "PMP-CASE-003-DCE-001"]} + } + }, + "missing_objects": [ + "DCE contents and specifications not inspected or acquired", + "Administrative and technical clauses not verified", + "Evaluation grid not available", + "Commission minutes and award not available for this open case", + "Programme prévisionnel link not established", + "Exact artifact bytes not acquired" + ] + }, + { + "candidate_id": "PMP-CASE-004", + "reference": "14/2026", + "public_buyer": "Secrétariat Général du Gouvernement", + "title_fr": "Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement", + "procurement_category": "GOODS_SUPPLIES", + "procedure_type": "Appel d'offres ouvert simplifié", + "publication_date": "2026-07-10", + "submission_deadline": "2026-07-22T10:30:00+01:00", + "official_portal_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u", + "structured_consultation_record": { + "evidence_id": "PMP-CASE-004-NOTICE", + "language": "fr", + "record_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u" + }, + "verified_tender_document_artifacts": [ + { + "artifact_id": "PMP-CASE-004-DCE-001", + "artifact_type": "DOSSIER_DE_CONSULTATION_DCE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1015612&orgAcronyme=g0u", + "source_candidate_id": "PMP-CASE-004", + "public_accessibility_state": "PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "form_submission_required_for_bytes": true, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "portal_displayed_size": "1.76 MiB", + "bytes_acquired": false + } + ], + "verified_commission_or_evaluation_artifacts": [ + { + "artifact_id": "PMP-CASE-004-PV-001", + "artifact_type": "PUBLIC_EXTRAIT_DE_PV", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1015612&orgAcronyme=g0u", + "source_candidate_id": "PMP-CASE-004", + "public_accessibility_state": "PUBLIC_STRUCTURED_HTML", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "bytes_acquired": false + } + ], + "verified_award_or_result_artifacts": [ + { + "artifact_id": "PMP-CASE-004-RESULT-001", + "artifact_type": "DEFINITIVE_RESULT_NOTICE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u", + "attached_notice_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDownloadAvisJAL&refConsultation=1029218&orgAcronyme=g0u&idAvis=527659", + "source_candidate_id": "PMP-CASE-004", + "public_accessibility_state": "PUBLIC_RESULT_RECORD_WITH_ATTACHED_NOTICE_LINK", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "bytes_acquired": false + } + ], + "state": "DEFINITIVE_RESULT_PUBLISHED", + "explicit_official_current_open_state": false, + "synthetic": false, + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "mandatory_gate": { + "eligible": true, + "criteria": { + "stable_official_procurement_reference": {"passed": true, "evidence_refs": ["reference", "official_portal_url"]}, + "official_french_notice_or_consultation_record": {"passed": true, "evidence_refs": ["PMP-CASE-004-NOTICE"]}, + "buyer_and_procedure_metadata": {"passed": true, "evidence_refs": ["public_buyer", "procedure_type"]}, + "verified_real_tender_document_artifact": {"passed": true, "evidence_refs": ["PMP-CASE-004-DCE-001"]}, + "verified_award_or_result_or_explicit_open_state": {"passed": true, "evidence_refs": ["PMP-CASE-004-RESULT-001"]}, + "no_login_captcha_or_access_control_bypass": {"passed": true, "evidence_refs": ["PMP-CASE-004-DCE-001", "PMP-CASE-004-PV-001", "PMP-CASE-004-RESULT-001"]}, + "no_synthetic_object": {"passed": true, "evidence_refs": ["synthetic"]}, + "all_urls_on_permitted_official_domain": {"passed": true, "evidence_refs": ["official_portal_url", "PMP-CASE-004-DCE-001", "PMP-CASE-004-PV-001", "PMP-CASE-004-RESULT-001"]} + } + }, + "missing_objects": [ + "DCE contents and specifications not inspected or acquired", + "Administrative and technical clauses not verified", + "Evaluation criteria grid not verified as a distinct artifact", + "Programme prévisionnel link not established", + "Exact artifact bytes not acquired" + ] + }, + { + "candidate_id": "PMP-CASE-005", + "reference": "13/2026", + "public_buyer": "Secrétariat Général du Gouvernement", + "title_fr": "Achat de toners pour le compte du Secrétariat Général du Gouvernement", + "procurement_category": "GOODS_SUPPLIES", + "procedure_type": "Appel d'offres ouvert simplifié", + "publication_date": "2026-06-19", + "submission_deadline": "2026-07-02T10:30:00+01:00", + "official_portal_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u", + "structured_consultation_record": { + "evidence_id": "PMP-CASE-005-NOTICE", + "language": "fr", + "record_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u" + }, + "verified_tender_document_artifacts": [ + { + "artifact_id": "PMP-CASE-005-DCE-001", + "artifact_type": "DOSSIER_DE_CONSULTATION_DCE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1010827&orgAcronyme=g0u", + "source_candidate_id": "PMP-CASE-005", + "public_accessibility_state": "PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "form_submission_required_for_bytes": true, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "portal_displayed_size": "1.7 MiB", + "bytes_acquired": false + } + ], + "verified_commission_or_evaluation_artifacts": [ + { + "artifact_id": "PMP-CASE-005-PV-001", + "artifact_type": "PUBLIC_EXTRAIT_DE_PV", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.ExtraitPV&refConsultation=1010827&orgAcronyme=g0u", + "source_candidate_id": "PMP-CASE-005", + "public_accessibility_state": "PUBLIC_STRUCTURED_HTML", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "bytes_acquired": false + } + ], + "verified_award_or_result_artifacts": [ + { + "artifact_id": "PMP-CASE-005-RESULT-001", + "artifact_type": "DEFINITIVE_RESULT_NOTICE", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u", + "attached_notice_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDownloadAvisJAL&refConsultation=1029211&orgAcronyme=g0u&idAvis=527649", + "source_candidate_id": "PMP-CASE-005", + "public_accessibility_state": "PUBLIC_RESULT_RECORD_WITH_ATTACHED_NOTICE_LINK", + "login_required": false, + "captcha_required": false, + "access_control_bypass_required": false, + "observed_utc": "2026-07-31T20:49:46Z", + "distinct_from_structured_consultation_record": true, + "bytes_acquired": false + } + ], + "state": "DEFINITIVE_RESULT_PUBLISHED", + "explicit_official_current_open_state": false, + "synthetic": false, + "engineering_disposition": "LINK_AND_RECEIPT_ONLY", + "mandatory_gate": { + "eligible": true, + "criteria": { + "stable_official_procurement_reference": {"passed": true, "evidence_refs": ["reference", "official_portal_url"]}, + "official_french_notice_or_consultation_record": {"passed": true, "evidence_refs": ["PMP-CASE-005-NOTICE"]}, + "buyer_and_procedure_metadata": {"passed": true, "evidence_refs": ["public_buyer", "procedure_type"]}, + "verified_real_tender_document_artifact": {"passed": true, "evidence_refs": ["PMP-CASE-005-DCE-001"]}, + "verified_award_or_result_or_explicit_open_state": {"passed": true, "evidence_refs": ["PMP-CASE-005-RESULT-001"]}, + "no_login_captcha_or_access_control_bypass": {"passed": true, "evidence_refs": ["PMP-CASE-005-DCE-001", "PMP-CASE-005-PV-001", "PMP-CASE-005-RESULT-001"]}, + "no_synthetic_object": {"passed": true, "evidence_refs": ["synthetic"]}, + "all_urls_on_permitted_official_domain": {"passed": true, "evidence_refs": ["official_portal_url", "PMP-CASE-005-DCE-001", "PMP-CASE-005-PV-001", "PMP-CASE-005-RESULT-001"]} + } + }, + "missing_objects": [ + "DCE contents and specifications not inspected or acquired", + "Administrative and technical clauses not verified", + "Evaluation criteria grid not verified as a distinct artifact", + "Programme prévisionnel link not established", + "Exact artifact bytes not acquired" + ] } ] } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md index 0f42550..d956527 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md @@ -1,42 +1,115 @@ # PMP Mission Candidates v0.1 -Status: **PUBLIC_PORTAL_METADATA_ONLY_NO_DOWNLOADS** +Status: **PUBLIC_PORTAL_EVIDENCE_ONLY_NO_DOWNLOADS** No procurement is classified as compliant or non-compliant. ## Candidates -| ID | Reference | Buyer | Procedure | Deadline | State | Portal | Objects | Disposition | -|---|---|---|---|---|---|---|---|---| -| PMP-CASE-001 | 44/FES/2026 | Direction Provinciale du MEN Fès | Appel d'offres ouvert | 2026-09-04T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | -| PMP-CASE-002 | 14/2026 | Directeur Régional d'Agriculture de la Région Béni Mellal-Khénifra | Appel d'offres ouvert simplifié | 2026-09-03T11:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | -| PMP-CASE-003 | 22/2026 | Ministère de la Justice | Appel d'offres ouvert | 2026-09-03T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408 | Official structured consultation notice in the public portal search result | LINK_AND_RECEIPT_ONLY | +| ID | Reference | Buyer | Procedure | Deadline | State | Portal | Tender artifacts | Commission artifacts | Result artifacts | Gate | Disposition | +|---|---|---|---|---|---|---|---|---|---|---|---| +| PMP-CASE-001 | 44/FES/2026 | Direction Provinciale du MEN Fès | Appel d'offres ouvert | 2026-09-04T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=p8x&page=entreprise.EntrepriseDetailConsultation&refConsultation=1024549 | PMP-CASE-001-DCE-001 | — | — | PASS | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-002 | 14/2026 | Directeur Régional d'Agriculture de la Région Béni Mellal-Khénifra | Appel d'offres ouvert simplifié | 2026-09-03T11:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=q1s&page=entreprise.EntrepriseDetailConsultation&refConsultation=1017000 | PMP-CASE-002-DCE-001 | — | — | PASS | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-003 | 22/2026 | Ministère de la Justice | Appel d'offres ouvert | 2026-09-03T10:00:00+01:00 | OPEN_DEADLINE_FUTURE_AT_OBSERVATION | https://www.marchespublics.gov.ma/index.php5?orgAcronyme=t5y&page=entreprise.EntrepriseDetailConsultation&refConsultation=1012408 | PMP-CASE-003-DCE-001 | — | — | PASS | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-004 | 14/2026 | Secrétariat Général du Gouvernement | Appel d'offres ouvert simplifié | 2026-07-22T10:30:00+01:00 | DEFINITIVE_RESULT_PUBLISHED | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u | PMP-CASE-004-DCE-001 | PMP-CASE-004-PV-001 | PMP-CASE-004-RESULT-001 | PASS | LINK_AND_RECEIPT_ONLY | +| PMP-CASE-005 | 13/2026 | Secrétariat Général du Gouvernement | Appel d'offres ouvert simplifié | 2026-07-02T10:30:00+01:00 | DEFINITIVE_RESULT_PUBLISHED | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u | PMP-CASE-005-DCE-001 | PMP-CASE-005-PV-001 | PMP-CASE-005-RESULT-001 | PASS | LINK_AND_RECEIPT_ONLY | + +## Verified tender-document artifacts + +| Candidate | Artifact | Type | Exact official URL | Public state | Login | CAPTCHA | Bypass | Observed | Bytes | +|---|---|---|---|---|---|---|---|---|---| +| PMP-CASE-001 | PMP-CASE-001-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1024549&orgAcronyme=p8x | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-002 | PMP-CASE-002-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1017000&orgAcronyme=q1s | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-003 | PMP-CASE-003-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1012408&orgAcronyme=t5y | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-004 | PMP-CASE-004-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1015612&orgAcronyme=g0u | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | +| PMP-CASE-005 | PMP-CASE-005-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1010827&orgAcronyme=g0u | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | + +## Evidence-derived mandatory gate + +| Candidate | Criterion | Passed | Evidence | +|---|---|---|---| +| PMP-CASE-001 | stable_official_procurement_reference | YES | reference; official_portal_url | +| PMP-CASE-001 | official_french_notice_or_consultation_record | YES | PMP-CASE-001-NOTICE | +| PMP-CASE-001 | buyer_and_procedure_metadata | YES | public_buyer; procedure_type | +| PMP-CASE-001 | verified_real_tender_document_artifact | YES | PMP-CASE-001-DCE-001 | +| PMP-CASE-001 | verified_award_or_result_or_explicit_open_state | YES | explicit_official_current_open_state | +| PMP-CASE-001 | no_login_captcha_or_access_control_bypass | YES | PMP-CASE-001-DCE-001 | +| PMP-CASE-001 | no_synthetic_object | YES | synthetic | +| PMP-CASE-001 | all_urls_on_permitted_official_domain | YES | official_portal_url; PMP-CASE-001-DCE-001 | +| PMP-CASE-002 | stable_official_procurement_reference | YES | reference; official_portal_url | +| PMP-CASE-002 | official_french_notice_or_consultation_record | YES | PMP-CASE-002-NOTICE | +| PMP-CASE-002 | buyer_and_procedure_metadata | YES | public_buyer; procedure_type | +| PMP-CASE-002 | verified_real_tender_document_artifact | YES | PMP-CASE-002-DCE-001 | +| PMP-CASE-002 | verified_award_or_result_or_explicit_open_state | YES | explicit_official_current_open_state | +| PMP-CASE-002 | no_login_captcha_or_access_control_bypass | YES | PMP-CASE-002-DCE-001 | +| PMP-CASE-002 | no_synthetic_object | YES | synthetic | +| PMP-CASE-002 | all_urls_on_permitted_official_domain | YES | official_portal_url; PMP-CASE-002-DCE-001 | +| PMP-CASE-003 | stable_official_procurement_reference | YES | reference; official_portal_url | +| PMP-CASE-003 | official_french_notice_or_consultation_record | YES | PMP-CASE-003-NOTICE | +| PMP-CASE-003 | buyer_and_procedure_metadata | YES | public_buyer; procedure_type | +| PMP-CASE-003 | verified_real_tender_document_artifact | YES | PMP-CASE-003-DCE-001 | +| PMP-CASE-003 | verified_award_or_result_or_explicit_open_state | YES | explicit_official_current_open_state | +| PMP-CASE-003 | no_login_captcha_or_access_control_bypass | YES | PMP-CASE-003-DCE-001 | +| PMP-CASE-003 | no_synthetic_object | YES | synthetic | +| PMP-CASE-003 | all_urls_on_permitted_official_domain | YES | official_portal_url; PMP-CASE-003-DCE-001 | +| PMP-CASE-004 | stable_official_procurement_reference | YES | reference; official_portal_url | +| PMP-CASE-004 | official_french_notice_or_consultation_record | YES | PMP-CASE-004-NOTICE | +| PMP-CASE-004 | buyer_and_procedure_metadata | YES | public_buyer; procedure_type | +| PMP-CASE-004 | verified_real_tender_document_artifact | YES | PMP-CASE-004-DCE-001 | +| PMP-CASE-004 | verified_award_or_result_or_explicit_open_state | YES | PMP-CASE-004-RESULT-001 | +| PMP-CASE-004 | no_login_captcha_or_access_control_bypass | YES | PMP-CASE-004-DCE-001; PMP-CASE-004-PV-001; PMP-CASE-004-RESULT-001 | +| PMP-CASE-004 | no_synthetic_object | YES | synthetic | +| PMP-CASE-004 | all_urls_on_permitted_official_domain | YES | official_portal_url; PMP-CASE-004-DCE-001; PMP-CASE-004-PV-001; PMP-CASE-004-RESULT-001 | +| PMP-CASE-005 | stable_official_procurement_reference | YES | reference; official_portal_url | +| PMP-CASE-005 | official_french_notice_or_consultation_record | YES | PMP-CASE-005-NOTICE | +| PMP-CASE-005 | buyer_and_procedure_metadata | YES | public_buyer; procedure_type | +| PMP-CASE-005 | verified_real_tender_document_artifact | YES | PMP-CASE-005-DCE-001 | +| PMP-CASE-005 | verified_award_or_result_or_explicit_open_state | YES | PMP-CASE-005-RESULT-001 | +| PMP-CASE-005 | no_login_captcha_or_access_control_bypass | YES | PMP-CASE-005-DCE-001; PMP-CASE-005-PV-001; PMP-CASE-005-RESULT-001 | +| PMP-CASE-005 | no_synthetic_object | YES | synthetic | +| PMP-CASE-005 | all_urls_on_permitted_official_domain | YES | official_portal_url; PMP-CASE-005-DCE-001; PMP-CASE-005-PV-001; PMP-CASE-005-RESULT-001 | ## Missing objects ### PMP-CASE-001 — 44/FES/2026 -- DAO/specifications not verified +- DCE contents and specifications not inspected or acquired - Administrative and technical clauses not verified - Evaluation grid not available -- Commission minutes and award not yet available +- Commission minutes and award not available for this open case - Programme prévisionnel link not established -- Exact artifact URLs and bytes not acquired +- Exact artifact bytes not acquired ### PMP-CASE-002 — 14/2026 -- DAO/specifications not verified +- DCE contents and specifications not inspected or acquired - Administrative and technical clauses not verified - Evaluation grid not available -- Commission minutes and award not yet available +- Commission minutes and award not available for this open case - Programme prévisionnel link not established -- Exact artifact URLs and bytes not acquired +- Exact artifact bytes not acquired ### PMP-CASE-003 — 22/2026 -- DAO/specifications not verified +- DCE contents and specifications not inspected or acquired - Administrative and technical clauses not verified - Evaluation grid not available -- Commission minutes and award not yet available +- Commission minutes and award not available for this open case +- Programme prévisionnel link not established +- Exact artifact bytes not acquired + +### PMP-CASE-004 — 14/2026 + +- DCE contents and specifications not inspected or acquired +- Administrative and technical clauses not verified +- Evaluation criteria grid not verified as a distinct artifact +- Programme prévisionnel link not established +- Exact artifact bytes not acquired + +### PMP-CASE-005 — 13/2026 + +- DCE contents and specifications not inspected or acquired +- Administrative and technical clauses not verified +- Evaluation criteria grid not verified as a distinct artifact - Programme prévisionnel link not established -- Exact artifact URLs and bytes not acquired +- Exact artifact bytes not acquired diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json index 4723362..e4b3547 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json @@ -1,15 +1,42 @@ { "document_id": "OAM-MOROCCO-PREFLIGHT-DECISION-v0.1", "status": "PROPOSED_PREFLIGHT_DECISION_NOT_SEMANTIC_ADMISSION", + "mission_selection_status": "ADMISSIBLE_CASES_FOUND", "exact_base": "29daa374b7e5cdc30ca7788310fbabb85f19912b", - "normative_candidates": ["MAR-NORM-001", "MAR-NORM-001-EN", "MAR-IMPL-001", "MAR-CCAG-001"], - "challenge_sources": ["CDC-CHALLENGE-001", "CDC-CHALLENGE-001-FR"], - "pmp_candidate_count": 3, - "preferred_mission": {"mission_id": "MAR-MISSION-001", "candidate_id": "PMP-CASE-001", "reference": "44/FES/2026"}, - "fallback_mission": {"candidate_id": "PMP-CASE-002", "reference": "14/2026"}, - "decision_basis": "Both nominees pass the bounded public-record gate through stable official identity, French notice metadata, buyer, procedure, publication date, deadline and explicit open state. DAO, evaluation and award evidence remain missing and are not inferred. No compliance conclusion is made.", + "normative_candidates": [ + "MAR-NORM-001", + "MAR-NORM-001-EN", + "MAR-IMPL-001" + ], + "excluded_normative_discovery_candidates": [ + { + "source_id": "MAR-CCAG-001", + "disposition": "DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION", + "reason": "Works CCAG does not match the selected goods mission" + } + ], + "challenge_sources": [ + "CDC-CHALLENGE-001", + "CDC-CHALLENGE-001-FR" + ], + "pmp_candidate_count": 5, + "preferred_mission": { + "mission_id": "MAR-MISSION-001", + "candidate_id": "PMP-CASE-004", + "reference": "14/2026" + }, + "fallback_mission": { + "candidate_id": "PMP-CASE-005", + "reference": "13/2026" + }, + "decision_basis": "The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate.", "no_source_bytes_acquired": true, + "no_tender_document_bytes_acquired": true, "no_semantic_processing": true, "semantic_implementation_gate": "BLOCKED", - "next_owner_decisions": ["Authorize rights review and exact-byte acquisition receipts", "Confirm works CCAG scope", "Confirm whether visible PV evidence is sufficient before artifact acquisition"] + "next_owner_decisions": [ + "Authorize rights review and exact-byte acquisition receipts", + "Decide whether to authorize acquisition of the preferred and fallback DCE, PV and result artifacts", + "Decide whether a separate applicable goods-sector instrument should be researched" + ] } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md index dabfe1c..7d6b0c7 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md @@ -4,16 +4,20 @@ Status: **PROPOSED_PREFLIGHT_DECISION_NOT_SEMANTIC_ADMISSION** Exact base: `29daa374b7e5cdc30ca7788310fbabb85f19912b` -PMP candidates: **3** +PMP candidates: **5** -Preferred: **MAR-MISSION-001 — 44/FES/2026** +Mission selection: **ADMISSIBLE_CASES_FOUND** -Fallback: **14/2026** +Preferred: **MAR-MISSION-001 — 14/2026** -Both nominees pass the bounded public-record gate through stable official identity, French notice metadata, buyer, procedure, publication date, deadline and explicit open state. DAO, evaluation and award evidence remain missing and are not inferred. No compliance conclusion is made. +Fallback: **13/2026** + +The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate. No source bytes acquired: **YES** +No tender-document bytes acquired: **YES** + No semantic processing: **YES** Semantic implementation gate: **BLOCKED** @@ -21,5 +25,5 @@ Semantic implementation gate: **BLOCKED** ## Owner decisions still required - Authorize rights review and exact-byte acquisition receipts -- Confirm works CCAG scope -- Confirm whether visible PV evidence is sufficient before artifact acquisition +- Decide whether to authorize acquisition of the preferred and fallback DCE, PV and result artifacts +- Decide whether a separate applicable goods-sector instrument should be researched diff --git a/tests/contract/test_morocco_preflight.py b/tests/contract/test_morocco_preflight.py index b7dc422..b7f3dea 100644 --- a/tests/contract/test_morocco_preflight.py +++ b/tests/contract/test_morocco_preflight.py @@ -6,6 +6,7 @@ import json import subprocess import sys +from collections.abc import Iterator from pathlib import Path from typing import Any, cast from urllib.parse import urlparse @@ -24,25 +25,120 @@ "worldbank.org", } LEGAL_CLEARANCE = "LEGALLY_CLEARED" +GATE_CRITERIA = ( + "stable_official_procurement_reference", + "official_french_notice_or_consultation_record", + "buyer_and_procedure_metadata", + "verified_real_tender_document_artifact", + "verified_award_or_result_or_explicit_open_state", + "no_login_captcha_or_access_control_bypass", + "no_synthetic_object", + "all_urls_on_permitted_official_domain", +) def load(name: str) -> dict[str, Any]: return cast(dict[str, Any], json.loads((PREFLIGHT / name).read_text(encoding="utf-8"))) +def official_url(value: object) -> bool: + if not isinstance(value, str) or not value.startswith("https://"): + return False + host = (urlparse(value).hostname or "").removeprefix("www.") + return host in PERMITTED + + +def iter_urls(value: object, key: str = "") -> Iterator[str]: + if isinstance(value, dict): + for child_key, child_value in value.items(): + yield from iter_urls(child_value, child_key) + elif isinstance(value, list): + for child in value: + yield from iter_urls(child, key) + elif isinstance(value, str) and key.endswith("url"): + yield value + + +def artifact_access_ok(artifact: dict[str, Any]) -> bool: + return ( + artifact.get("login_required") is False + and artifact.get("captcha_required") is False + and artifact.get("access_control_bypass_required") is False + ) + + +def verified_tender_artifact(case: dict[str, Any], artifact: dict[str, Any]) -> bool: + return ( + artifact.get("artifact_type") == "DOSSIER_DE_CONSULTATION_DCE" + and artifact.get("source_candidate_id") == case.get("candidate_id") + and official_url(artifact.get("exact_official_url")) + and bool(artifact.get("public_accessibility_state")) + and bool(artifact.get("observed_utc")) + and artifact.get("distinct_from_structured_consultation_record") is True + and artifact.get("bytes_acquired") is False + ) + + +def verified_award_artifact(case: dict[str, Any], artifact: dict[str, Any]) -> bool: + return ( + artifact.get("artifact_type") == "DEFINITIVE_RESULT_NOTICE" + and artifact.get("source_candidate_id") == case.get("candidate_id") + and official_url(artifact.get("exact_official_url")) + and bool(artifact.get("public_accessibility_state")) + and bool(artifact.get("observed_utc")) + and artifact.get("distinct_from_structured_consultation_record") is True + and artifact.get("bytes_acquired") is False + ) + + +def derive_gate(case: dict[str, Any]) -> dict[str, bool]: + notice = case.get("structured_consultation_record", {}) + tenders = case.get("verified_tender_document_artifacts", []) + commission = case.get("verified_commission_or_evaluation_artifacts", []) + awards = case.get("verified_award_or_result_artifacts", []) + urls = list(iter_urls(case)) + access_objects = [*tenders, *commission, *awards] + criteria = { + "stable_official_procurement_reference": bool(case.get("reference")) + and official_url(case.get("official_portal_url")), + "official_french_notice_or_consultation_record": notice.get("language") == "fr" + and notice.get("record_type") == "OFFICIAL_STRUCTURED_CONSULTATION_RECORD" + and official_url(notice.get("exact_official_url")), + "buyer_and_procedure_metadata": bool(case.get("public_buyer")) + and bool(case.get("procedure_type")), + "verified_real_tender_document_artifact": any( + verified_tender_artifact(case, artifact) for artifact in tenders + ), + "verified_award_or_result_or_explicit_open_state": any( + verified_award_artifact(case, artifact) for artifact in awards + ) + or case.get("explicit_official_current_open_state") is True, + "no_login_captcha_or_access_control_bypass": bool(access_objects) + and all(artifact_access_ok(artifact) for artifact in access_objects), + "no_synthetic_object": case.get("synthetic") is False, + "all_urls_on_permitted_official_domain": bool(urls) + and all(official_url(url) for url in urls), + } + assert tuple(criteria) == GATE_CRITERIA + return criteria + + def validate( - register: dict[str, Any], candidates: dict[str, Any], scorecard: dict[str, Any] + register: dict[str, Any], + candidates: dict[str, Any], + scorecard: dict[str, Any], + normative: dict[str, Any], + decision: dict[str, Any], ) -> None: sources = register["sources"] - ids = [source["source_id"] for source in sources] - assert len(ids) == len(set(ids)) + source_ids = [source["source_id"] for source in sources] + assert len(source_ids) == len(set(source_ids)) assert LEGAL_CLEARANCE not in register["engineering_dispositions"] for source in sources: assert source["language"] for key in ("canonical_url", "download_url"): if source[key] is not None: - host = (urlparse(source[key]).hostname or "").removeprefix("www.") - assert host in PERMITTED + assert official_url(source[key]) assert source["engineering_disposition"] in register["engineering_dispositions"] assert source["engineering_disposition"] != LEGAL_CLEARANCE if source["language"] == "en": @@ -51,41 +147,119 @@ def validate( assert "NORMATIVE_TEXT" not in source["authority_rank"] cases = candidates["candidates"] + candidate_ids = [case["candidate_id"] for case in cases] assert 3 <= len(cases) <= 7 - assert all(not case["synthetic"] for case in cases) - assert all(case["login_requirement_for_page"] != "REQUIRED" for case in cases) - assert all(case["associated_public_objects"] for case in cases) - assert all(case["state"] for case in cases) - assert all( - case["publication_date"] and "UNRESOLVED" not in case["publication_date"] for case in cases - ) + assert len(candidate_ids) == len(set(candidate_ids)) + assert all(case["publication_date"] for case in cases) + assert all(case["procurement_category"] == "GOODS_SUPPLIES" for case in cases) + assert all("mandatory_minimum" not in case for case in cases) + + observations = register["mission_evidence_observations"] + assert [item["candidate_id"] for item in observations] == candidate_ids + for observation in observations: + assert observation["bytes_acquired"] is False + assert all(official_url(url) for url in iter_urls(observation)) + + derived_by_id: dict[str, dict[str, bool]] = {} + for case in cases: + criteria = derive_gate(case) + derived_by_id[case["candidate_id"]] = criteria + stored = case["mandatory_gate"] + assert tuple(stored["criteria"]) == GATE_CRITERIA + assert { + criterion: result["passed"] for criterion, result in stored["criteria"].items() + } == criteria + assert stored["eligible"] is all(criteria.values()) + assert all( + not artifact["bytes_acquired"] + for artifact in case["verified_tender_document_artifacts"] + ) dimensions = scorecard["dimensions"] assert len(dimensions) == 14 assert len(scorecard["scores"]) == len(cases) - assert ( - sum(score["nomination"] == "PREFERRED_MAR-MISSION-001" for score in scorecard["scores"]) - == 1 - ) - assert sum(score["nomination"] == "FALLBACK" for score in scorecard["scores"]) == 1 - for score in scorecard["scores"]: + score_by_id = {score["candidate_id"]: score for score in scorecard["scores"]} + assert set(score_by_id) == set(candidate_ids) + award_index = dimensions.index("award_or_result") + commission_index = dimensions.index("commission_minute_or_extract") + exact_artifact_index = dimensions.index("exact_byte_downloadable_artifacts") + suitability_index = dimensions.index("tender_evaluation_award_suitability") + for case in cases: + score = score_by_id[case["candidate_id"]] + assert "mandatory_minimum" not in score assert len(score["values"]) == len(dimensions) assert all(value in {0, 1, 2} for value in score["values"]) assert score["total"] == sum(score["values"]) - if score["nomination"] in {"PREFERRED_MAR-MISSION-001", "FALLBACK"}: - assert score["mandatory_minimum"] + gate_passed = all(derived_by_id[case["candidate_id"]].values()) + assert score["mandatory_gate_result"] == ("PASS" if gate_passed else "FAIL") + assert score["gate_evidence_reference"] == f"{case['candidate_id']}#mandatory_gate" + award_artifacts = case["verified_award_or_result_artifacts"] + commission_artifacts = case["verified_commission_or_evaluation_artifacts"] + assert all(verified_award_artifact(case, artifact) for artifact in award_artifacts) + assert score["values"][award_index] == (2 if award_artifacts else 0) + assert score["values"][commission_index] == (2 if commission_artifacts else 0) + assert score["values"][exact_artifact_index] > 0 + assert all( + verified_tender_artifact(case, artifact) + for artifact in case["verified_tender_document_artifacts"] + ) + if not award_artifacts and not commission_artifacts: + assert score["values"][suitability_index] <= 1 + if score["nomination"] != "NOT_SELECTED": + assert gate_passed + + preferred_scores = [ + score for score in scorecard["scores"] if score["nomination"] == "PREFERRED_MAR-MISSION-001" + ] + fallback_scores = [score for score in scorecard["scores"] if score["nomination"] == "FALLBACK"] + if scorecard["selection_outcome"] == "NO_ADMISSIBLE_REAL_CASE": + assert scorecard["preferred"] is None + assert scorecard["fallback"] is None + assert not preferred_scores + assert not fallback_scores + else: + assert scorecard["selection_outcome"] == "ADMISSIBLE_CASES_FOUND" + assert len(preferred_scores) == 1 + assert len(fallback_scores) == 1 + assert preferred_scores[0]["candidate_id"] == scorecard["preferred"]["candidate_id"] + assert fallback_scores[0]["candidate_id"] == scorecard["fallback"]["candidate_id"] + + ccag = next(entry for entry in normative["entries"] if entry["source_id"] == "MAR-CCAG-001") + assert ccag["role"] == "DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION" + assert ccag["authoritative_for_semantics"] is False + assert "MAR-CCAG-001" not in normative["proposed_normative_corpus"] + assert any( + entry["source_id"] == "MAR-CCAG-001" + for entry in normative["excluded_from_proposed_normative_corpus"] + ) + + assert decision["pmp_candidate_count"] == len(cases) + assert decision["mission_selection_status"] == scorecard["selection_outcome"] + assert decision["preferred_mission"]["candidate_id"] == scorecard["preferred"]["candidate_id"] + assert decision["fallback_mission"]["candidate_id"] == scorecard["fallback"]["candidate_id"] + assert decision["normative_candidates"] == normative["proposed_normative_corpus"] + assert decision["no_source_bytes_acquired"] is True + assert decision["no_tender_document_bytes_acquired"] is True + assert decision["no_semantic_processing"] is True + assert decision["semantic_implementation_gate"] == "BLOCKED" @pytest.fixture -def bundle() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: +def bundle() -> tuple[ + dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any] +]: return ( load("OFFICIAL-SOURCE-REGISTER-v0.1.json"), load("PMP-MISSION-CANDIDATES-v0.1.json"), load("MISSION-SELECTION-SCORECARD-v0.1.json"), + load("NORMATIVE-SOURCE-MAP-v0.1.json"), + load("PREFLIGHT-DECISION-v0.1.json"), ) -def test_bundle_contract(bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]]) -> None: +def test_bundle_contract( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], +) -> None: validate(*bundle) @@ -105,6 +279,7 @@ def test_no_source_bytes_committed() -> None: assert all(path.suffix in allowed for path in PREFLIGHT.iterdir()) decision = load("PREFLIGHT-DECISION-v0.1.json") assert decision["no_source_bytes_acquired"] is True + assert decision["no_tender_document_bytes_acquired"] is True def test_protected_artifacts_unchanged() -> None: @@ -125,7 +300,7 @@ def test_no_semantic_artifact_created() -> None: def test_source_mutations_fail( - bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]], + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], ) -> None: mutations = ( "third_party", @@ -135,7 +310,7 @@ def test_source_mutations_fail( "legal_clearance", ) for mutation in mutations: - register, candidates, scorecard = copy.deepcopy(bundle) + register, candidates, scorecard, normative, decision = copy.deepcopy(bundle) if mutation == "third_party": register["sources"][0]["canonical_url"] = "https://example.com/mirror.pdf" elif mutation == "authoritative_translation": @@ -147,11 +322,11 @@ def test_source_mutations_fail( else: register["sources"][0]["engineering_disposition"] = LEGAL_CLEARANCE with pytest.raises(AssertionError): - validate(register, candidates, scorecard) + validate(register, candidates, scorecard, normative, decision) -def test_mission_mutations_fail( - bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any]], +def test_existing_mission_mutations_fail( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], ) -> None: mutations = ( "missing_artifact", @@ -161,16 +336,114 @@ def test_mission_mutations_fail( "mandatory_bypass", ) for mutation in mutations: - register, candidates, scorecard = copy.deepcopy(bundle) + register, candidates, scorecard, normative, decision = copy.deepcopy(bundle) if mutation == "missing_artifact": - candidates["candidates"][0]["associated_public_objects"] = [] + candidates["candidates"][3]["verified_tender_document_artifacts"] = [] elif mutation == "inflated_score": - scorecard["scores"][0]["total"] += 1 + scorecard["scores"][3]["total"] += 1 elif mutation == "synthetic": - candidates["candidates"][0]["synthetic"] = True + candidates["candidates"][3]["synthetic"] = True elif mutation == "login_required": - candidates["candidates"][0]["login_requirement_for_page"] = "REQUIRED" + candidates["candidates"][3]["verified_tender_document_artifacts"][0][ + "login_required" + ] = True else: - scorecard["scores"][0]["mandatory_minimum"] = False - with pytest.raises(AssertionError): - validate(register, candidates, scorecard) + candidates["candidates"][3]["mandatory_gate"]["eligible"] = False + try: + validate(register, candidates, scorecard, normative, decision) + except AssertionError: + pass + else: + raise AssertionError(f"mutation survived validation: {mutation}") + + +def test_evidence_gate_correction_mutations_fail( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], +) -> None: + mutations = ( + "unsupported_manual_mandatory_true", + "empty_verified_tender_artifacts", + "structured_notice_only", + "non_official_tender_url", + "tender_login_required", + "award_score_without_award", + "open_state_as_award_evidence", + "nomination_with_failed_criterion", + "preferred_present_under_no_admissible", + "works_ccag_applicable_without_binding", + "aggregate_score_overrides_gate", + ) + for mutation in mutations: + register, candidates, scorecard, normative, decision = copy.deepcopy(bundle) + if mutation == "unsupported_manual_mandatory_true": + scorecard["scores"][3]["mandatory_minimum"] = True + candidates["candidates"][3]["verified_tender_document_artifacts"] = [] + elif mutation == "empty_verified_tender_artifacts": + candidates["candidates"][3]["verified_tender_document_artifacts"] = [] + elif mutation == "structured_notice_only": + case = candidates["candidates"][3] + case["verified_tender_document_artifacts"] = [ + { + "artifact_id": "NOTICE-AS-DCE", + "artifact_type": "OFFICIAL_STRUCTURED_CONSULTATION_RECORD", + "exact_official_url": case["structured_consultation_record"][ + "exact_official_url" + ], + "source_candidate_id": case["candidate_id"], + "public_accessibility_state": "PUBLIC_STRUCTURED_HTML", + "login_required": False, + "captcha_required": False, + "access_control_bypass_required": False, + "observed_utc": candidates["observed_utc"], + "distinct_from_structured_consultation_record": False, + "bytes_acquired": False, + } + ] + elif mutation == "non_official_tender_url": + candidates["candidates"][3]["verified_tender_document_artifacts"][0][ + "exact_official_url" + ] = "https://example.com/dce" + elif mutation == "tender_login_required": + candidates["candidates"][3]["verified_tender_document_artifacts"][0][ + "login_required" + ] = True + elif mutation == "award_score_without_award": + scorecard["scores"][0]["values"][8] = 2 + scorecard["scores"][0]["total"] += 2 + elif mutation == "open_state_as_award_evidence": + case = candidates["candidates"][0] + case["verified_award_or_result_artifacts"] = [ + { + "artifact_id": "OPEN-STATE", + "artifact_type": "OPEN_STATE", + "exact_official_url": case["official_portal_url"], + "source_candidate_id": case["candidate_id"], + "public_accessibility_state": "PUBLIC_STRUCTURED_HTML", + "login_required": False, + "captcha_required": False, + "access_control_bypass_required": False, + "observed_utc": candidates["observed_utc"], + "distinct_from_structured_consultation_record": False, + "bytes_acquired": False, + } + ] + scorecard["scores"][0]["values"][8] = 2 + scorecard["scores"][0]["total"] += 2 + elif mutation == "nomination_with_failed_criterion": + candidates["candidates"][3]["public_buyer"] = "" + elif mutation == "preferred_present_under_no_admissible": + scorecard["selection_outcome"] = "NO_ADMISSIBLE_REAL_CASE" + elif mutation == "works_ccag_applicable_without_binding": + next(entry for entry in normative["entries"] if entry["source_id"] == "MAR-CCAG-001")[ + "role" + ] = "APPLICABLE_TO_CURRENT_MISSION" + else: + candidates["candidates"][3]["verified_tender_document_artifacts"] = [] + scorecard["scores"][3]["values"] = [2] * 14 + scorecard["scores"][3]["total"] = 28 + try: + validate(register, candidates, scorecard, normative, decision) + except AssertionError: + pass + else: + raise AssertionError(f"mutation survived validation: {mutation}") diff --git a/tools/render_morocco_preflight.py b/tools/render_morocco_preflight.py index a25fe36..6043c59 100644 --- a/tools/render_morocco_preflight.py +++ b/tools/render_morocco_preflight.py @@ -85,6 +85,30 @@ def render_source_register(data: dict[str, Any]) -> str: for source in sources ], ) + lines += ["", "## Mission evidence observations", ""] + lines += _table( + [ + "Candidate", + "Consultation", + "Tender artifact", + "Commission artifact", + "Result artifact", + "Observed", + "Bytes", + ], + [ + [ + observation["candidate_id"], + observation["consultation_record_url"], + observation["verified_tender_artifact_url"], + observation["verified_commission_artifact_url"], + observation["verified_result_artifact_url"], + observation["observed_utc"], + observation["bytes_acquired"], + ] + for observation in data["mission_evidence_observations"] + ], + ) return "\n".join(lines) + "\n" @@ -111,6 +135,17 @@ def render_normative_map(data: dict[str, Any]) -> str: for entry in data["entries"] ], ) + lines += ["", "## Proposed normative corpus", ""] + [ + f"- {source_id}" for source_id in data["proposed_normative_corpus"] + ] + lines += ["", "## Excluded discovery candidates", ""] + lines += _table( + ["ID", "Reason"], + [ + [entry["source_id"], entry["reason"]] + for entry in data["excluded_from_proposed_normative_corpus"] + ], + ) lines += ["", "## Unresolved", ""] + [f"- {item}" for item in data["unresolved"]] return "\n".join(lines) + "\n" @@ -135,7 +170,10 @@ def render_candidates(data: dict[str, Any]) -> str: "Deadline", "State", "Portal", - "Objects", + "Tender artifacts", + "Commission artifacts", + "Result artifacts", + "Gate", "Disposition", ], [ @@ -147,12 +185,58 @@ def render_candidates(data: dict[str, Any]) -> str: case["submission_deadline"], case["state"], case["official_portal_url"], - case["associated_public_objects"], + [item["artifact_id"] for item in case["verified_tender_document_artifacts"]], + [ + item["artifact_id"] + for item in case["verified_commission_or_evaluation_artifacts"] + ], + [item["artifact_id"] for item in case["verified_award_or_result_artifacts"]], + "PASS" if case["mandatory_gate"]["eligible"] else "FAIL", case["engineering_disposition"], ] for case in data["candidates"] ], ) + lines += ["", "## Verified tender-document artifacts", ""] + lines += _table( + [ + "Candidate", + "Artifact", + "Type", + "Exact official URL", + "Public state", + "Login", + "CAPTCHA", + "Bypass", + "Observed", + "Bytes", + ], + [ + [ + case["candidate_id"], + artifact["artifact_id"], + artifact["artifact_type"], + artifact["exact_official_url"], + artifact["public_accessibility_state"], + artifact["login_required"], + artifact["captcha_required"], + artifact["access_control_bypass_required"], + artifact["observed_utc"], + artifact["bytes_acquired"], + ] + for case in data["candidates"] + for artifact in case["verified_tender_document_artifacts"] + ], + ) + lines += ["", "## Evidence-derived mandatory gate", ""] + lines += _table( + ["Candidate", "Criterion", "Passed", "Evidence"], + [ + [case["candidate_id"], criterion, result["passed"], result["evidence_refs"]] + for case in data["candidates"] + for criterion, result in case["mandatory_gate"]["criteria"].items() + ], + ) lines += ["", "## Missing objects", ""] for case in data["candidates"]: lines += ( @@ -182,17 +266,27 @@ def render_scorecard(data: dict[str, Any]) -> str: score["candidate_id"], *score["values"], score["total"], - score["mandatory_minimum"], + score["mandatory_gate_result"], score["nomination"], ] for score in data["scores"] ], ) + preferred = data["preferred"] + fallback = data["fallback"] + lines += ["", f"Outcome: **{data['selection_outcome']}**", ""] lines += [ + ( + f"Preferred: **{preferred['mission_id']} → {preferred['candidate_id']}**" + if preferred is not None + else "Preferred: **none**" + ), "", - f"Preferred: **{data['preferred']['mission_id']} → {data['preferred']['candidate_id']}**", - "", - f"Fallback: **{data['fallback']['candidate_id']}**", + ( + f"Fallback: **{fallback['candidate_id']}**" + if fallback is not None + else "Fallback: **none**" + ), "", ] return "\n".join(lines) @@ -244,6 +338,11 @@ def render_constraints(data: dict[str, Any]) -> str: def render_decision(data: dict[str, Any]) -> str: preferred = data["preferred_mission"] + fallback = data["fallback_mission"] + preferred_text = ( + f"{preferred['mission_id']} — {preferred['reference']}" if preferred is not None else "none" + ) + fallback_text = fallback["reference"] if fallback is not None else "none" return "\n".join( [ "# Morocco Preflight Decision v0.1", @@ -254,14 +353,19 @@ def render_decision(data: dict[str, Any]) -> str: "", f"PMP candidates: **{data['pmp_candidate_count']}**", "", - f"Preferred: **{preferred['mission_id']} — {preferred['reference']}**", + f"Mission selection: **{data['mission_selection_status']}**", "", - f"Fallback: **{data['fallback_mission']['reference']}**", + f"Preferred: **{preferred_text}**", + "", + f"Fallback: **{fallback_text}**", "", data["decision_basis"], "", f"No source bytes acquired: **{_cell(data['no_source_bytes_acquired'])}**", "", + "No tender-document bytes acquired: " + f"**{_cell(data['no_tender_document_bytes_acquired'])}**", + "", f"No semantic processing: **{_cell(data['no_semantic_processing'])}**", "", f"Semantic implementation gate: **{data['semantic_implementation_gate']}**", From 38b16e4db311852c15421e8fc2044e2568299b1a Mon Sep 17 00:00:00 2001 From: //AM Date: Fri, 31 Jul 2026 17:38:57 -0400 Subject: [PATCH 3/3] research: bind Morocco result record lineage --- .../MISSION-SELECTION-SCORECARD-v0.1.json | 12 +- .../MISSION-SELECTION-SCORECARD-v0.1.md | 12 + .../PMP-MISSION-CANDIDATES-v0.1.json | 168 ++++++++++++++ .../PMP-MISSION-CANDIDATES-v0.1.md | 7 + .../PREFLIGHT-DECISION-v0.1.json | 15 +- .../preflight-v0.1/PREFLIGHT-DECISION-v0.1.md | 14 +- tests/contract/test_morocco_preflight.py | 218 +++++++++++++++++- tools/render_morocco_preflight.py | 68 ++++++ 8 files changed, 504 insertions(+), 10 deletions(-) diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json index a4d977b..f2132dd 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.json @@ -5,6 +5,12 @@ "1": "partial_or_uncertain", "2": "present_and_official" }, + "mission_selection_status": "PROVISIONAL_UNRECONCILED", + "proposed_mission_id": "MAR-MISSION-001", + "institutional_admission": "NONE", + "golden_mission_status": "NOT_ADMITTED", + "cross_repository_reconciliation_required": true, + "semantic_implementation_gate": "BLOCKED", "dimensions": [ "stable_official_identity", "french_source_availability", @@ -67,9 +73,11 @@ "selection_outcome": "ADMISSIBLE_CASES_FOUND", "preferred": { "mission_id": "MAR-MISSION-001", - "candidate_id": "PMP-CASE-004" + "candidate_id": "PMP-CASE-004", + "result_linkage_result": "VERIFIED_MATCH" }, "fallback": { - "candidate_id": "PMP-CASE-005" + "candidate_id": "PMP-CASE-005", + "result_linkage_result": "VERIFIED_MATCH" } } diff --git a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md index 20e9a3c..1a5a32c 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/MISSION-SELECTION-SCORECARD-v0.1.md @@ -2,6 +2,18 @@ Scores: 0 absent; 1 partial/uncertain; 2 present and official. +Mission status: **PROVISIONAL_UNRECONCILED** + +Proposed mission ID: **MAR-MISSION-001** + +Institutional admission: **NONE** + +Golden mission: **NOT_ADMITTED** + +Cross-repository reconciliation required: **YES** + +Semantic implementation gate: **BLOCKED** + Rule: Derive the eight mandatory criteria from candidate evidence before scoring. Aggregate score never overrides the mandatory gate. Open state is recorded separately and never counted as award evidence. ## Complete scorecard diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json index f38fd02..52a13af 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.json @@ -243,6 +243,8 @@ "attached_notice_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDownloadAvisJAL&refConsultation=1029218&orgAcronyme=g0u&idAvis=527659", "source_candidate_id": "PMP-CASE-004", "public_accessibility_state": "PUBLIC_RESULT_RECORD_WITH_ATTACHED_NOTICE_LINK", + "cross_record_linkage_id": "PMP-CASE-004-LINK-001", + "result_evidence_eligible": true, "login_required": false, "captcha_required": false, "access_control_bypass_required": false, @@ -251,6 +253,88 @@ "bytes_acquired": false } ], + "cross_record_linkage": { + "linkage_id": "PMP-CASE-004-LINK-001", + "original_consultation_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u", + "result_record_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u", + "original_refConsultation": "1015612", + "result_refConsultation": "1029218", + "observed_procurement_reference": { + "original_record": "14/2026", + "result_record": "14/2026" + }, + "observed_buyer": { + "original_record": { + "display_name": null, + "display_name_observation": "NOT_EXPOSED_AS_A_DISTINCT_FIELD_ON_PUBLIC_RECORD", + "portal_organization_acronym": "g0u" + }, + "result_record": { + "display_name": null, + "display_name_observation": "NOT_EXPOSED_AS_A_DISTINCT_FIELD_ON_PUBLIC_RECORD", + "portal_organization_acronym": "g0u" + } + }, + "observed_procurement_object": { + "original_record": "Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement", + "result_record": "Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement" + }, + "observed_procedure_or_lot_information": { + "original_record": null, + "result_record": null, + "observation": "NOT_EXPOSED_ON_EITHER_PUBLIC_RECORD" + }, + "explicit_portal_linkage_field": { + "field_name": null, + "value": null, + "observation": "NO_EXPLICIT_ORIGINAL_TO_RESULT_LINKAGE_FIELD_EXPOSED" + }, + "normalized_matching_fields": [ + { + "field": "procurement_reference", + "original": "14/2026", + "result": "14/2026", + "evidence_refs": ["PMP-CASE-004-LINK-ORIGINAL", "PMP-CASE-004-LINK-RESULT"] + }, + { + "field": "buyer_portal_organization_acronym", + "original": "g0u", + "result": "g0u", + "evidence_refs": ["PMP-CASE-004-LINK-ORIGINAL", "PMP-CASE-004-LINK-RESULT"] + }, + { + "field": "procurement_object", + "original": "achat de matériel de bureau pour le compte du secrétariat général du gouvernement", + "result": "achat de matériel de bureau pour le compte du secrétariat général du gouvernement", + "evidence_refs": ["PMP-CASE-004-LINK-ORIGINAL", "PMP-CASE-004-LINK-RESULT"] + } + ], + "mismatch_fields": [ + { + "field": "refConsultation", + "original": "1015612", + "result": "1029218", + "recorded_linkage_basis": "Separate portal record identifiers are reconciled by the exact matching procurement reference, procurement object and portal organization acronym.", + "evidence_refs": ["PMP-CASE-004-LINK-ORIGINAL", "PMP-CASE-004-LINK-RESULT"] + } + ], + "evidence_references": [ + { + "evidence_id": "PMP-CASE-004-LINK-ORIGINAL", + "record_role": "ORIGINAL_CONSULTATION", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u", + "observed_fields": ["refConsultation=1015612", "reference=14/2026", "orgAcronyme=g0u", "object=Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement"] + }, + { + "evidence_id": "PMP-CASE-004-LINK-RESULT", + "record_role": "DEFINITIVE_RESULT", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u", + "observed_fields": ["refConsultation=1029218", "reference=14/2026", "orgAcronyme=g0u", "object=Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement"] + } + ], + "observed_utc": "2026-07-31T21:23:40Z", + "linkage_result": "VERIFIED_MATCH" + }, "state": "DEFINITIVE_RESULT_PUBLISHED", "explicit_official_current_open_state": false, "synthetic": false, @@ -332,6 +416,8 @@ "attached_notice_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDownloadAvisJAL&refConsultation=1029211&orgAcronyme=g0u&idAvis=527649", "source_candidate_id": "PMP-CASE-005", "public_accessibility_state": "PUBLIC_RESULT_RECORD_WITH_ATTACHED_NOTICE_LINK", + "cross_record_linkage_id": "PMP-CASE-005-LINK-001", + "result_evidence_eligible": true, "login_required": false, "captcha_required": false, "access_control_bypass_required": false, @@ -340,6 +426,88 @@ "bytes_acquired": false } ], + "cross_record_linkage": { + "linkage_id": "PMP-CASE-005-LINK-001", + "original_consultation_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u", + "result_record_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u", + "original_refConsultation": "1010827", + "result_refConsultation": "1029211", + "observed_procurement_reference": { + "original_record": "13/2026", + "result_record": "13/2026" + }, + "observed_buyer": { + "original_record": { + "display_name": null, + "display_name_observation": "NOT_EXPOSED_AS_A_DISTINCT_FIELD_ON_PUBLIC_RECORD", + "portal_organization_acronym": "g0u" + }, + "result_record": { + "display_name": null, + "display_name_observation": "NOT_EXPOSED_AS_A_DISTINCT_FIELD_ON_PUBLIC_RECORD", + "portal_organization_acronym": "g0u" + } + }, + "observed_procurement_object": { + "original_record": "Achat de toners pour le compte du Secrétariat Général du Gouvernement", + "result_record": "Achat de toners pour le compte du Secrétariat Général du Gouvernement" + }, + "observed_procedure_or_lot_information": { + "original_record": null, + "result_record": null, + "observation": "NOT_EXPOSED_ON_EITHER_PUBLIC_RECORD" + }, + "explicit_portal_linkage_field": { + "field_name": null, + "value": null, + "observation": "NO_EXPLICIT_ORIGINAL_TO_RESULT_LINKAGE_FIELD_EXPOSED" + }, + "normalized_matching_fields": [ + { + "field": "procurement_reference", + "original": "13/2026", + "result": "13/2026", + "evidence_refs": ["PMP-CASE-005-LINK-ORIGINAL", "PMP-CASE-005-LINK-RESULT"] + }, + { + "field": "buyer_portal_organization_acronym", + "original": "g0u", + "result": "g0u", + "evidence_refs": ["PMP-CASE-005-LINK-ORIGINAL", "PMP-CASE-005-LINK-RESULT"] + }, + { + "field": "procurement_object", + "original": "achat de toners pour le compte du secrétariat général du gouvernement", + "result": "achat de toners pour le compte du secrétariat général du gouvernement", + "evidence_refs": ["PMP-CASE-005-LINK-ORIGINAL", "PMP-CASE-005-LINK-RESULT"] + } + ], + "mismatch_fields": [ + { + "field": "refConsultation", + "original": "1010827", + "result": "1029211", + "recorded_linkage_basis": "Separate portal record identifiers are reconciled by the exact matching procurement reference, procurement object and portal organization acronym.", + "evidence_refs": ["PMP-CASE-005-LINK-ORIGINAL", "PMP-CASE-005-LINK-RESULT"] + } + ], + "evidence_references": [ + { + "evidence_id": "PMP-CASE-005-LINK-ORIGINAL", + "record_role": "ORIGINAL_CONSULTATION", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u", + "observed_fields": ["refConsultation=1010827", "reference=13/2026", "orgAcronyme=g0u", "object=Achat de toners pour le compte du Secrétariat Général du Gouvernement"] + }, + { + "evidence_id": "PMP-CASE-005-LINK-RESULT", + "record_role": "DEFINITIVE_RESULT", + "exact_official_url": "https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u", + "observed_fields": ["refConsultation=1029211", "reference=13/2026", "orgAcronyme=g0u", "object=Achat de toners pour le compte du Secrétariat Général du Gouvernement"] + } + ], + "observed_utc": "2026-07-31T21:23:40Z", + "linkage_result": "VERIFIED_MATCH" + }, "state": "DEFINITIVE_RESULT_PUBLISHED", "explicit_official_current_open_state": false, "synthetic": false, diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md index d956527..5e7e1c5 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/PMP-MISSION-CANDIDATES-v0.1.md @@ -24,6 +24,13 @@ No procurement is classified as compliant or non-compliant. | PMP-CASE-004 | PMP-CASE-004-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1015612&orgAcronyme=g0u | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | | PMP-CASE-005 | PMP-CASE-005-DCE-001 | DOSSIER_DE_CONSULTATION_DCE | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDemandeTelechargementDce&refConsultation=1010827&orgAcronyme=g0u | PUBLIC_LINK_TO_DOWNLOAD_REQUEST_FORM | NO | NO | NO | 2026-07-31T20:49:46Z | NO | +## Cross-record result lineage + +| Candidate | Linkage | Original record | Result record | Original refConsultation | Result refConsultation | Reference match | Buyer portal ID match | Object match | Mismatch fields | Explicit portal linkage | Result | Observed | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| PMP-CASE-004 | PMP-CASE-004-LINK-001 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1015612&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029218&orgAcronyme=g0u | 1015612 | 1029218 | {'original_record': '14/2026', 'result_record': '14/2026'} | {'original': 'g0u', 'result': 'g0u'} | {'original_record': 'Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement', 'result_record': 'Achat de matériel de bureau pour le compte du Secrétariat Général du Gouvernement'} | {'field': 'refConsultation', 'original': '1015612', 'result': '1029218', 'recorded_linkage_basis': 'Separate portal record identifiers are reconciled by the exact matching procurement reference, procurement object and portal organization acronym.', 'evidence_refs': ['PMP-CASE-004-LINK-ORIGINAL', 'PMP-CASE-004-LINK-RESULT']} | {'field_name': None, 'value': None, 'observation': 'NO_EXPLICIT_ORIGINAL_TO_RESULT_LINKAGE_FIELD_EXPOSED'} | VERIFIED_MATCH | 2026-07-31T21:23:40Z | +| PMP-CASE-005 | PMP-CASE-005-LINK-001 | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1010827&orgAcronyme=g0u | https://www.marchespublics.gov.ma/index.php?page=entreprise.EntrepriseDetailConsultation&refConsultation=1029211&orgAcronyme=g0u | 1010827 | 1029211 | {'original_record': '13/2026', 'result_record': '13/2026'} | {'original': 'g0u', 'result': 'g0u'} | {'original_record': 'Achat de toners pour le compte du Secrétariat Général du Gouvernement', 'result_record': 'Achat de toners pour le compte du Secrétariat Général du Gouvernement'} | {'field': 'refConsultation', 'original': '1010827', 'result': '1029211', 'recorded_linkage_basis': 'Separate portal record identifiers are reconciled by the exact matching procurement reference, procurement object and portal organization acronym.', 'evidence_refs': ['PMP-CASE-005-LINK-ORIGINAL', 'PMP-CASE-005-LINK-RESULT']} | {'field_name': None, 'value': None, 'observation': 'NO_EXPLICIT_ORIGINAL_TO_RESULT_LINKAGE_FIELD_EXPOSED'} | VERIFIED_MATCH | 2026-07-31T21:23:40Z | + ## Evidence-derived mandatory gate | Candidate | Criterion | Passed | Evidence | diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json index e4b3547..e104edd 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.json @@ -1,7 +1,12 @@ { "document_id": "OAM-MOROCCO-PREFLIGHT-DECISION-v0.1", "status": "PROPOSED_PREFLIGHT_DECISION_NOT_SEMANTIC_ADMISSION", - "mission_selection_status": "ADMISSIBLE_CASES_FOUND", + "mission_selection_status": "PROVISIONAL_UNRECONCILED", + "mission_selection_outcome": "ADMISSIBLE_CASES_FOUND", + "proposed_mission_id": "MAR-MISSION-001", + "institutional_admission": "NONE", + "golden_mission_status": "NOT_ADMITTED", + "cross_repository_reconciliation_required": true, "exact_base": "29daa374b7e5cdc30ca7788310fbabb85f19912b", "normative_candidates": [ "MAR-NORM-001", @@ -23,13 +28,15 @@ "preferred_mission": { "mission_id": "MAR-MISSION-001", "candidate_id": "PMP-CASE-004", - "reference": "14/2026" + "reference": "14/2026", + "result_linkage_result": "VERIFIED_MATCH" }, "fallback_mission": { "candidate_id": "PMP-CASE-005", - "reference": "13/2026" + "reference": "13/2026", + "result_linkage_result": "VERIFIED_MATCH" }, - "decision_basis": "The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate.", + "decision_basis": "The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record whose cross-record lineage is VERIFIED_MATCH through the exact procurement reference, procurement object and portal organization acronym. The differing refConsultation identifiers are recorded explicitly. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate. This nomination is provisional and remains unreconciled with the separate OAM CDC repository.", "no_source_bytes_acquired": true, "no_tender_document_bytes_acquired": true, "no_semantic_processing": true, diff --git a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md index 7d6b0c7..421811b 100644 --- a/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md +++ b/benchmarks/corpus/morocco/preflight-v0.1/PREFLIGHT-DECISION-v0.1.md @@ -6,13 +6,23 @@ Exact base: `29daa374b7e5cdc30ca7788310fbabb85f19912b` PMP candidates: **5** -Mission selection: **ADMISSIBLE_CASES_FOUND** +Mission selection: **PROVISIONAL_UNRECONCILED** + +Selection outcome: **ADMISSIBLE_CASES_FOUND** + +Proposed mission ID: **MAR-MISSION-001** + +Institutional admission: **NONE** + +Golden mission: **NOT_ADMITTED** + +Cross-repository reconciliation required: **YES** Preferred: **MAR-MISSION-001 — 14/2026** Fallback: **13/2026** -The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate. +The preferred and fallback candidates independently pass the evidence-derived eight-criterion gate. Each has an official French consultation record, a distinct verified DCE link, a public Extrait de PV and a separate definitive-result record whose cross-record lineage is VERIFIED_MATCH through the exact procurement reference, procurement object and portal organization acronym. The differing refConsultation identifiers are recorded explicitly. The three retained open candidates pass through their explicit official open state and distinct DCE links but have no award evidence. Aggregate scores did not override the gate. This nomination is provisional and remains unreconciled with the separate OAM CDC repository. No source bytes acquired: **YES** diff --git a/tests/contract/test_morocco_preflight.py b/tests/contract/test_morocco_preflight.py index b7f3dea..890f186 100644 --- a/tests/contract/test_morocco_preflight.py +++ b/tests/contract/test_morocco_preflight.py @@ -9,7 +9,7 @@ from collections.abc import Iterator from pathlib import Path from typing import Any, cast -from urllib.parse import urlparse +from urllib.parse import parse_qs, urlparse import pytest @@ -48,6 +48,129 @@ def official_url(value: object) -> bool: return host in PERMITTED +def normalized(value: object) -> str: + return " ".join(value.casefold().split()) if isinstance(value, str) else "" + + +def portal_record_fields(value: object) -> dict[str, str] | None: + if not official_url(value): + return None + parsed = urlparse(cast(str, value)) + query = parse_qs(parsed.query) + fields = { + "page": query.get("page", [""])[0], + "refConsultation": query.get("refConsultation", [""])[0], + "orgAcronyme": query.get("orgAcronyme", [""])[0], + } + return fields if all(fields.values()) else None + + +def derive_result_linkage(case: dict[str, Any], artifact: dict[str, Any]) -> str: + linkage = case.get("cross_record_linkage") + if not isinstance(linkage, dict): + return "NOT_VERIFIED" + original_url = linkage.get("original_consultation_url") + result_url = linkage.get("result_record_url") + original_fields = portal_record_fields(original_url) + result_fields = portal_record_fields(result_url) + if original_fields is None or result_fields is None: + return "NOT_VERIFIED" + if original_fields["page"] != "entreprise.EntrepriseDetailConsultation": + return "NOT_VERIFIED" + if result_fields["page"] != "entreprise.EntrepriseDetailConsultation": + return "NOT_VERIFIED" + if original_url != case.get("official_portal_url"): + return "NOT_VERIFIED" + if result_url != artifact.get("exact_official_url"): + return "NOT_VERIFIED" + if original_fields["refConsultation"] != linkage.get("original_refConsultation"): + return "NOT_VERIFIED" + if result_fields["refConsultation"] != linkage.get("result_refConsultation"): + return "NOT_VERIFIED" + if original_fields["refConsultation"] == result_fields["refConsultation"]: + return "NOT_VERIFIED" + + evidence = linkage.get("evidence_references", []) + evidence_by_id = {item.get("evidence_id"): item for item in evidence if isinstance(item, dict)} + expected_evidence_ids = { + f"{case['candidate_id']}-LINK-ORIGINAL", + f"{case['candidate_id']}-LINK-RESULT", + } + if set(evidence_by_id) != expected_evidence_ids: + return "NOT_VERIFIED" + if ( + evidence_by_id[f"{case['candidate_id']}-LINK-ORIGINAL"].get("exact_official_url") + != original_url + ): + return "NOT_VERIFIED" + if ( + evidence_by_id[f"{case['candidate_id']}-LINK-RESULT"].get("exact_official_url") + != result_url + ): + return "NOT_VERIFIED" + + observed_reference = linkage.get("observed_procurement_reference", {}) + observed_buyer = linkage.get("observed_buyer", {}) + observed_object = linkage.get("observed_procurement_object", {}) + original_buyer = observed_buyer.get("original_record", {}) + result_buyer = observed_buyer.get("result_record", {}) + expected_matches = { + "procurement_reference": ( + normalized(observed_reference.get("original_record")), + normalized(observed_reference.get("result_record")), + normalized(case.get("reference")), + ), + "buyer_portal_organization_acronym": ( + normalized(original_buyer.get("portal_organization_acronym")), + normalized(result_buyer.get("portal_organization_acronym")), + normalized(original_fields["orgAcronyme"]), + ), + "procurement_object": ( + normalized(observed_object.get("original_record")), + normalized(observed_object.get("result_record")), + normalized(case.get("title_fr")), + ), + } + matching_fields = linkage.get("normalized_matching_fields", []) + matching_by_name = { + item.get("field"): item for item in matching_fields if isinstance(item, dict) + } + if set(matching_by_name) != set(expected_matches): + return "NOT_VERIFIED" + for field, (original, result, expected) in expected_matches.items(): + stored = matching_by_name[field] + if not original or original != result or original != expected: + return "NOT_VERIFIED" + if normalized(stored.get("original")) != original: + return "NOT_VERIFIED" + if normalized(stored.get("result")) != result: + return "NOT_VERIFIED" + if set(stored.get("evidence_refs", [])) != expected_evidence_ids: + return "NOT_VERIFIED" + if normalized(result_buyer.get("portal_organization_acronym")) != normalized( + result_fields["orgAcronyme"] + ): + return "NOT_VERIFIED" + + mismatches = linkage.get("mismatch_fields", []) + if len(mismatches) != 1 or mismatches[0].get("field") != "refConsultation": + return "NOT_VERIFIED" + mismatch = mismatches[0] + if mismatch.get("original") != original_fields["refConsultation"]: + return "NOT_VERIFIED" + if mismatch.get("result") != result_fields["refConsultation"]: + return "NOT_VERIFIED" + if not mismatch.get("recorded_linkage_basis"): + return "NOT_VERIFIED" + if set(mismatch.get("evidence_refs", [])) != expected_evidence_ids: + return "NOT_VERIFIED" + if not linkage.get("observed_utc"): + return "NOT_VERIFIED" + if artifact.get("cross_record_linkage_id") != linkage.get("linkage_id"): + return "NOT_VERIFIED" + return "VERIFIED_MATCH" + + def iter_urls(value: object, key: str = "") -> Iterator[str]: if isinstance(value, dict): for child_key, child_value in value.items(): @@ -88,6 +211,9 @@ def verified_award_artifact(case: dict[str, Any], artifact: dict[str, Any]) -> b and bool(artifact.get("observed_utc")) and artifact.get("distinct_from_structured_consultation_record") is True and artifact.get("bytes_acquired") is False + and derive_result_linkage(case, artifact) == "VERIFIED_MATCH" + and case.get("cross_record_linkage", {}).get("linkage_result") == "VERIFIED_MATCH" + and artifact.get("result_evidence_eligible") is True ) @@ -207,6 +333,8 @@ def validate( assert score["values"][suitability_index] <= 1 if score["nomination"] != "NOT_SELECTED": assert gate_passed + assert award_artifacts + assert all(verified_award_artifact(case, artifact) for artifact in award_artifacts) preferred_scores = [ score for score in scorecard["scores"] if score["nomination"] == "PREFERRED_MAR-MISSION-001" @@ -223,6 +351,20 @@ def validate( assert len(fallback_scores) == 1 assert preferred_scores[0]["candidate_id"] == scorecard["preferred"]["candidate_id"] assert fallback_scores[0]["candidate_id"] == scorecard["fallback"]["candidate_id"] + preferred_case = next( + case for case in cases if case["candidate_id"] == scorecard["preferred"]["candidate_id"] + ) + fallback_case = next( + case for case in cases if case["candidate_id"] == scorecard["fallback"]["candidate_id"] + ) + assert ( + scorecard["preferred"]["result_linkage_result"] + == preferred_case["cross_record_linkage"]["linkage_result"] + ) + assert ( + scorecard["fallback"]["result_linkage_result"] + == fallback_case["cross_record_linkage"]["linkage_result"] + ) ccag = next(entry for entry in normative["entries"] if entry["source_id"] == "MAR-CCAG-001") assert ccag["role"] == "DISCOVERY_CANDIDATE_NOT_APPLICABLE_TO_CURRENT_MISSION" @@ -234,14 +376,32 @@ def validate( ) assert decision["pmp_candidate_count"] == len(cases) - assert decision["mission_selection_status"] == scorecard["selection_outcome"] + assert scorecard["mission_selection_status"] == "PROVISIONAL_UNRECONCILED" + assert decision["mission_selection_status"] == "PROVISIONAL_UNRECONCILED" + assert decision["mission_selection_outcome"] == scorecard["selection_outcome"] + assert decision["proposed_mission_id"] == scorecard["proposed_mission_id"] + assert decision["institutional_admission"] == "NONE" + assert scorecard["institutional_admission"] == "NONE" + assert decision["golden_mission_status"] == "NOT_ADMITTED" + assert scorecard["golden_mission_status"] == "NOT_ADMITTED" + assert decision["cross_repository_reconciliation_required"] is True + assert scorecard["cross_repository_reconciliation_required"] is True assert decision["preferred_mission"]["candidate_id"] == scorecard["preferred"]["candidate_id"] assert decision["fallback_mission"]["candidate_id"] == scorecard["fallback"]["candidate_id"] + assert ( + decision["preferred_mission"]["result_linkage_result"] + == scorecard["preferred"]["result_linkage_result"] + ) + assert ( + decision["fallback_mission"]["result_linkage_result"] + == scorecard["fallback"]["result_linkage_result"] + ) assert decision["normative_candidates"] == normative["proposed_normative_corpus"] assert decision["no_source_bytes_acquired"] is True assert decision["no_tender_document_bytes_acquired"] is True assert decision["no_semantic_processing"] is True assert decision["semantic_implementation_gate"] == "BLOCKED" + assert scorecard["semantic_implementation_gate"] == "BLOCKED" @pytest.fixture @@ -447,3 +607,57 @@ def test_evidence_gate_correction_mutations_fail( pass else: raise AssertionError(f"mutation survived validation: {mutation}") + + +def test_result_lineage_mutations_fail( + bundle: tuple[dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any], dict[str, Any]], +) -> None: + mutations = ( + "different_official_result_url", + "different_result_reference", + "different_result_buyer", + "different_result_object", + "different_ref_without_linkage_basis", + "manual_verified_unsupported", + "award_score_for_not_verified", + "nomination_on_unlinked_result", + ) + for mutation in mutations: + register, candidates, scorecard, normative, decision = copy.deepcopy(bundle) + case = candidates["candidates"][3] + linkage = case["cross_record_linkage"] + result = case["verified_award_or_result_artifacts"][0] + if mutation == "different_official_result_url": + substituted = ( + "https://www.marchespublics.gov.ma/index.php?" + "page=entreprise.EntrepriseDetailConsultation&" + "refConsultation=1029211&orgAcronyme=g0u" + ) + result["exact_official_url"] = substituted + linkage["result_record_url"] = substituted + elif mutation == "different_result_reference": + linkage["observed_procurement_reference"]["result_record"] = "13/2026" + elif mutation == "different_result_buyer": + linkage["observed_buyer"]["result_record"]["portal_organization_acronym"] = "different" + elif mutation == "different_result_object": + linkage["observed_procurement_object"]["result_record"] = ( + "Achat de toners pour le compte d'une autre institution" + ) + elif mutation == "different_ref_without_linkage_basis": + del linkage["mismatch_fields"][0]["recorded_linkage_basis"] + elif mutation == "manual_verified_unsupported": + linkage["normalized_matching_fields"][0]["result"] = "13/2026" + linkage["linkage_result"] = "VERIFIED_MATCH" + elif mutation == "award_score_for_not_verified": + linkage["linkage_result"] = "NOT_VERIFIED" + result["result_evidence_eligible"] = False + else: + linkage["linkage_result"] = "NOT_VERIFIED" + result["result_evidence_eligible"] = False + scorecard["scores"][3]["nomination"] = "PREFERRED_MAR-MISSION-001" + try: + validate(register, candidates, scorecard, normative, decision) + except AssertionError: + pass + else: + raise AssertionError(f"mutation survived validation: {mutation}") diff --git a/tools/render_morocco_preflight.py b/tools/render_morocco_preflight.py index 6043c59..4b507bb 100644 --- a/tools/render_morocco_preflight.py +++ b/tools/render_morocco_preflight.py @@ -228,6 +228,50 @@ def render_candidates(data: dict[str, Any]) -> str: for artifact in case["verified_tender_document_artifacts"] ], ) + lines += ["", "## Cross-record result lineage", ""] + lines += _table( + [ + "Candidate", + "Linkage", + "Original record", + "Result record", + "Original refConsultation", + "Result refConsultation", + "Reference match", + "Buyer portal ID match", + "Object match", + "Mismatch fields", + "Explicit portal linkage", + "Result", + "Observed", + ], + [ + [ + case["candidate_id"], + case["cross_record_linkage"]["linkage_id"], + case["cross_record_linkage"]["original_consultation_url"], + case["cross_record_linkage"]["result_record_url"], + case["cross_record_linkage"]["original_refConsultation"], + case["cross_record_linkage"]["result_refConsultation"], + case["cross_record_linkage"]["observed_procurement_reference"], + { + "original": case["cross_record_linkage"]["observed_buyer"]["original_record"][ + "portal_organization_acronym" + ], + "result": case["cross_record_linkage"]["observed_buyer"]["result_record"][ + "portal_organization_acronym" + ], + }, + case["cross_record_linkage"]["observed_procurement_object"], + case["cross_record_linkage"]["mismatch_fields"], + case["cross_record_linkage"]["explicit_portal_linkage_field"], + case["cross_record_linkage"]["linkage_result"], + case["cross_record_linkage"]["observed_utc"], + ] + for case in data["candidates"] + if "cross_record_linkage" in case + ], + ) lines += ["", "## Evidence-derived mandatory gate", ""] lines += _table( ["Candidate", "Criterion", "Passed", "Evidence"], @@ -254,6 +298,19 @@ def render_scorecard(data: dict[str, Any]) -> str: "", "Scores: 0 absent; 1 partial/uncertain; 2 present and official.", "", + f"Mission status: **{data['mission_selection_status']}**", + "", + f"Proposed mission ID: **{data['proposed_mission_id']}**", + "", + f"Institutional admission: **{data['institutional_admission']}**", + "", + f"Golden mission: **{data['golden_mission_status']}**", + "", + "Cross-repository reconciliation required: " + f"**{_cell(data['cross_repository_reconciliation_required'])}**", + "", + f"Semantic implementation gate: **{data['semantic_implementation_gate']}**", + "", f"Rule: {data['selection_rule']}", "", "## Complete scorecard", @@ -355,6 +412,17 @@ def render_decision(data: dict[str, Any]) -> str: "", f"Mission selection: **{data['mission_selection_status']}**", "", + f"Selection outcome: **{data['mission_selection_outcome']}**", + "", + f"Proposed mission ID: **{data['proposed_mission_id']}**", + "", + f"Institutional admission: **{data['institutional_admission']}**", + "", + f"Golden mission: **{data['golden_mission_status']}**", + "", + "Cross-repository reconciliation required: " + f"**{_cell(data['cross_repository_reconciliation_required'])}**", + "", f"Preferred: **{preferred_text}**", "", f"Fallback: **{fallback_text}**",