com um clique
suggest-target-ruleset
Suggest Target Rulesets given Policy Documents
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Suggest Target Rulesets given Policy Documents
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
Emit Catala
Create Sample Tests
Draft or Update Test Cases
Expand Test Coverage
Extract Test Cases from Policy Documents
Generate a Demo App (Catala-Python Backend)
| name | suggest-target-ruleset |
| description | Suggest Target Rulesets given Policy Documents |
Analyze a domain's policy_facets/computations/ per-file files and suggest 1–3 candidate target rulesets based on all their information, including section headings, summary, topic tags, and computation hints. Saves suggestion files to specs/suggested_targets/. The user will select one of the suggestion files as the input to /declare-target-ruleset.
The optional <hint> argument (e.g., "eligibility" or "benefit calculation") narrows what kinds of rulesets to suggest — it prioritizes candidates that match the hint phrase but still reads the whole index. When no hint is provided, all distinct policy scopes found in the index are candidates with a preference for rulesets that cover more topics.
/suggest-target-ruleset <domain> [<hint>]
Read ../../core/output-fencing.md now.
Run these checks before doing anything else:
Domain argument provided?
$DOMAINS_DIR/*/ as a numbered menu and prompt:
:::user_input
Available domains:
<domain>. Then continue.Domain folder exists?
Per-file computations present?
$DOMAINS_DIR/<domain>/policy_facets/computations/ exists and contains at least one *.md.yaml file (recursive).Glob every *.md.yaml file under $DOMAINS_DIR/<domain>/policy_facets/computations/ sorted lexicographically by relative path (e.g., Python sorted(glob(...))) — glob defaults to OS-dependent order on macOS/Linux/CI, so explicit sorting is required for deterministic observation ordering downstream. Parse each as a YAML map. Read data["sections"] as the list of {heading, summary, tags, computations?, variables?} section blocks. The source path of each section is encoded in the file's relative path under policy_facets/computations/ — strip the trailing .yaml to recover <rel>.md, then prefix with input/policy_docs/. (A section in policy_facets/computations/sub/foo.md.yaml describes input/policy_docs/sub/foo.md.)
Legacy on-disk files may carry a top-level naming_manifest: key from prior versions; this is silently ignored. The section-level variables: block (post-3.0) is the canonical source of per-variable verbatim phrases — read it alongside computations: per the aggregation rule below.
Do NOT read files under $DOMAINS_DIR/<domain>/input/ — policy_facets/computations/ is the sole source of doc signals.
Cluster the index signals to identify 1–5 distinct policy scopes. For each scope, derive a candidate target ruleset:
expr_hint: parse rule (uniform across consumer skills): when a computation carries expr_hint:, split on the first =; the LHS (whitespace-trimmed) is the snake_case output name for that computation, and the RHS is the expression. Tokenize the RHS for snake_case identifiers (skipping numeric literals, string literals, and built-in keywords like if, else, and, or, not, min, max, sum) — those identifiers are the input names. When expr_hint: is absent (descriptive-only computation), fall back to scanning description: prose for variable names mentioned in the source's terminology.
Signals to extract:
expr_hint: parse rule above to collect (output name, input names) pairs. Variables appearing only on the RHS of any expr_hint: (and never on an LHS) are leaf inputs; variables appearing on an LHS are computed (intermediate or final) outputs. Variables that appear on an LHS in one computation and on the RHS of another are intermediate computed variables.computations: entries from sections that have the field; trace variable chains via the expr_hint: parse rule above (LHS-of-one is RHS-of-another → intermediate computed); collect expr_hint: RHS values keyed by their LHS output name; collect preconditions: expressions keyed by their computation's output name. Recurring precondition clauses across many entries (e.g., a large cluster of computations all gated on "applicant is over 65") signal a distinct policy scope and should yield a separate candidate ruleset rather than being mixed into a more general one. If the index has no computations: entries, skip this signal.stage: value (when present). Apply the same suffix-stripping normalization as /create-ruleset-groups (drop a trailing _test / _check / _evaluation). When ≥1 section has a stage: value, treat distinct (post-normalization) stages as primary clustering boundaries for candidate target rulesets — distinct stages that span large variable counts are strong signals for distinct candidate rulesets, stronger than tag/heading similarity alone. Do not merge candidates across distinct stage: values; stage boundaries are explicit doc signals, while tag/heading clusters are inferred. When no section has stage:, fall through to existing tag/heading/computation-hint clustering unchanged.For each candidate, derive:
ruleset_name — snake_case base filename (e.g., eligibility_check, income_calculation)display_name — human-readable title (e.g., "Determine Program Eligibility")description — one sentence describing what the ruleset computesrole — AI persona for extraction (e.g., "You are a policy-to-rules analyst for eligibility determination.")scope — extraction goal sentence (e.g., "Convert the provided policy text into explicit, testable eligibility rules that produce an eligibility decision.")inputs.<EntityName>.<field> — entity-grouped input variables. See "Entity inference" below for the rule. Each <field> is keyed by snake_case variable name and carries optional {type, description, observations}.computed.<field> — flat block of computed (intermediate) variables. Variables identified as intermediate via the variable-chain analysis above (LHS-of-one, RHS-of-another) flow here. Each entry carries optional {type, description, observations}.outputs.<field> — flat block of output variables. Each entry carries optional {type, description, observations}. List the candidate's main decision first (the primary output); secondary outputs (denial reasons, verification flags, etc.) follow in subsequent entries. Variables that appear on an LHS but never on a downstream RHS are terminal outputs. Declaration order is load-bearing — downstream /create-skeleton treats the first entry as primary.Observation aggregation for <field>.observations. For every variable that becomes an entry under inputs.*.*, computed.*, or outputs.*:
*.md.yaml file under policy_facets/computations/ in the sorted iteration order from this step's opening paragraph.variables: block contains the variable's snake_case name as a key. (Sections that pre-date the post-3.0 variables: block — older files without that field — contribute no observations.){policy_phrase, source_doc, section} where:
policy_phrase: is the value of variables[<name>].policy_phrase when present; omit the key when the per-file entry has no policy_phrase (phrase-absent observation — see U1's no-fallback rule).source_doc: is the reconstituted source path (input/policy_docs/<rel>.md) per the path-encoding rule above.section: is the section's heading: field verbatim (including the # / ## / ### prefix).observations: list. Deduplicate on the (policy_phrase, source_doc, section) triple — distinct triples become distinct observations; identical triples are kept once.sections: list.variables: block (synthesized / algorithm-derived output names, or variables the AI surfaced from description: prose but never persisted in variables:), omit observations: from the entry entirely — do not emit an empty list. Downstream consumers (/declare-target-ruleset → naming-manifest) treat the absent field as "no source observation recorded," and /extract-ruleset Step 3b renders such rows with the <seeded> placeholder.Multi-observation entries are normal and expected — a variable referenced across multiple policy doc sections naturally yields a multi-element list.
Entity inference for inputs.<EntityName>.<field>. Inputs are the leaf variables — those that appear on the RHS of some expr_hint: or in description: prose but never on an LHS, plus variables in descriptive-only computations that the source treats as supplied rather than computed. For each input, determine its owning entity from policy doc context. Entities are CamelCase nouns representing the conceptual owner of their fields — common examples: Applicant, Household, Income, Asset, Resource. Use these signals in order:
Cross-source reuse pattern (highest priority, applied before the other rules). Scan section summaries, computation descriptions, and preconditions: clauses for phrases that compare or reconcile two parallel sources of the same data:
When the pattern is detected, emit two (or more) parallel entities sharing a mirrored field schema, one per source — do not collapse them into a single merged entity. Examples:
ClientStatement + <Agency>Record (e.g., DOLRecord, AVSRecord, IRSRecord)ApplicantReport + ThirdPartyReportEmployerReport + SelfReportBoth entities carry the same field names (e.g., ClientStatement.gross_earned_income and DOLRecord.gross_earned_income); the policy's "apply X to both" language IS the explicit signal of a reusable computation module that /create-ruleset-modules's reuse_across_entities heuristic (priority 1) will detect downstream. Collapsing them into a single Income entity hides this signal and prevents module detection. When Rule 0 fires, rules 1–3 below still apply to other inputs that don't participate in the cross-source comparison.
Section heading and surrounding section text for the variable's source — a variable surfaced under a "Household composition" heading likely belongs to Household; a variable under "Applicant demographics" likely belongs to Applicant.
The variable's source policy phrase (the verbatim noun phrase the source uses for the concept) — phrasing like "applicant's age" → Applicant.age; "household size" → Household.household_size.
Variable name semantics — e.g., a variable whose name starts with a clear entity prefix may indicate ownership when section context is ambiguous, but do NOT rely on prefix alone — gross_income is not owned by a Gross entity.
When the entity is unclear or ambiguous after applying the above signals, emit the field under the fallback entity Case. Do not invent a one-off entity per variable to avoid the fallback — Case exists exactly for variables that don't have a clear conceptual owner. Analysts can regroup entities during /declare-target-ruleset confirm.
Type inference. For each variable, the AI may infer a Catala-native type from policy doc context (e.g., "monthly amount" → money; a yes/no field → boolean; whole-number count → integer; ratio / percentage → decimal; "as of" date → date; a bulleted enumeration → enum; repeated values → list; compound record → structure). When inference fails, omit type: rather than guess — downstream consumers tolerate a missing type.
Description. Optional per field. Emit when there is a clear signal in the source policy text (e.g., a definition or contextual phrase). Omit when no signal exists rather than fabricate.
When <hint> is provided:
Suggestions focused on: <hint>policy_facets/computations/ — the hint is a prioritization signal, not a filter that discards unrelated sections entirely(No strong match found for "<hint>" — showing all candidates)Display all candidates in a numbered list:
:::detail Suggestions focused on: ← omit this line if no hint was provided
<ruleset_name> Inputs: Output: <first_output_name> ()
<ruleset_name> Inputs: Output: <first_output_name> ()
<ruleset_name> ← include only if a third distinct scope is identifiable ... :::
Save each candidate:
Ensure $DOMAINS_DIR/<domain>/specs/suggested_targets/ directory exists. Create it if absent.
Write $DOMAINS_DIR/<domain>/specs/suggested_targets/<ruleset_name>.yaml using exactly this schema:
# Auto-generated by /suggest-target-ruleset — do not edit manually
ruleset_name: <snake_case>
display_name: <string>
description: <string>
role: <string>
scope: <string>
inputs:
<EntityName>: # CamelCase noun (Applicant, Household, Income, ...). Fallback: Case.
<field_name>: # snake_case variable name
type: integer | decimal | money | boolean | date | duration | string | enum | list | structure # optional; omit when no signal
description: <string> # optional
observations: # optional; multi-source phrase-level provenance per Step 1's aggregation rule. Omit entirely when no source observation recorded.
- policy_phrase: <string> # optional inside an observation — omit when the per-file variables: entry had no verbatim phrase
source_doc: input/policy_docs/<rel>.md
section: <verbatim heading including # / ## / ### prefix>
# repeat for additional observations across sections / files
# repeat for each field under this entity
# repeat for each entity
computed: # flat — no entity grouping
<field_name>:
type: <type> # optional
description: <string> # optional
observations: # optional; same shape as inputs entries
- policy_phrase: <string>
source_doc: input/policy_docs/<rel>.md
section: <heading>
outputs: # flat — list the primary output first; declaration order is load-bearing
<field_name>:
type: <type> # optional
description: <string> # optional
observations: # optional; omit on synthesized outputs that have no source observation
- policy_phrase: <string>
source_doc: input/policy_docs/<rel>.md
section: <heading>
# repeat for each output
YAML conventions:
description: and display_name: values as quoted stringsoutputs: — /create-skeleton treats the first output as primary; secondary outputs followtype: and description: when no signal exists rather than guessingobservations: entirely when the variable was never observed in any section's variables: block — do not emit observations: []. Empty list and missing key are semantically the same; the convention is to omit. Synthesized output names (e.g., a top-level eligibility_status not directly named in any source body) commonly land without observations: and surface downstream as <seeded> placeholders in the Name Inventory.observations: entry, policy_phrase: is independently optional — omit it when the upstream variables: block recorded the variable without a verbatim phrase. source_doc: and section: ship together (paired-or-absent) per the merge tool's per-observation invariant.Case for input fields with no clear conceptual owner — do not invent one-off entities to avoid the fallback# Generated: date as YYYY-MM-DD (today's date)After all saves, suggest the next step:
:::next_step Next: Run /declare-target-ruleset <ruleset_name> to create the guidance/ files from a suggestion file. :::
:::important $DOMAINS_DIR//specs/suggested_targets/<ruleset_name>.yaml [CREATED] :::
(one line per saved file)
intermediate_variables, constraints, standards, guidance, edge_cases, skeleton:, ruleset_groups:, or ruleset_modules: in suggestion files — those are written by later skills (/create-skeleton, /create-ruleset-groups, /create-ruleset-modules)$DOMAINS_DIR/<domain>/input/ — policy_facets/computations/ is the sole source of doc signalsprimary: flag on outputs.<field> entries — primary distinction is encoded by declaration order (first entry = primary); /create-skeleton reads that order when writing guidance/output-variables.yaml. Emitting primary: adds noise that downstream tooling strips.type: when no signal exists — omit the field instead. Same for description:.observations: entries — only emit observations for variables that appear in a per-file YAML's section-level variables: block. The block is the canonical source of phrase-level provenance; do not synthesize observations from headings, descriptions, or expr_hint tokens alone.observations: [] — when a variable has no source observation (synthesized output, or never persisted in any variables: block), omit the observations: key entirely. Empty list and missing key are semantically equivalent; the convention is to omit.policy_facets/computations/**/*.md.yaml iteration order must be lexicographic by relative path (use sorted(glob(...)) in Python). OS-dependent file ordering produces non-deterministic observation order across macOS / Linux / CI, which propagates into the manifest and surfaces as spurious diffs across re-runs.Case fallback — Case is the correct entity for input fields with no clear conceptual owner. Splintering into Misc1, Misc2, etc. is worse than using Case.ClientStatement + DOLRecord), not one merged Income or Data entity. Merging hides the reuse signal that /create-ruleset-modules's reuse_across_entities heuristic needs to detect a shared sub-module. This is Entity Inference Rule 0 — the highest-priority rule, applied before heading/policy-phrase/name signals.computed: is flat. Computed values are functions of multiple entities' inputs; they don't conceptually belong to one entity.type: values — type: enum not type:\n - approve\n - deny$DOMAINS_DIR from .xlator.local.env if the variable is unknownstage: values — stage: is an explicit doc signal; merging two stage-tagged scopes into one candidate ruleset discards information the analyst already encoded in the sourcestage: or modify it — stage: is single-owner; only /extract-computations writes the field. This skill reads it