default: "<h1>Patron Blocking Rules — Allowed Functions</h1>\n<p>Patron blocking rule expressions are evaluated by a locked-down\n<a href=\"https://github.com/danthedeckie/simpleeval\">simpleeval</a> sandbox.\nOnly the functions listed below may be called inside a rule expression.\nAny reference to an unlisted function causes the rule to <strong>fail open</strong>\n(the patron is not blocked at runtime; the rule is accepted at admin-save time,\nthough there is feedback in the Admin UI warning the user that there is a problem\nwith the rule).</p>\n<hr>\n<h2><code>age_in_years</code></h2>\n<p>Calculates the age of a person in <strong>whole years</strong> from a date string.\nUse this to write rules that gate access by age (e.g. block minors or\nenforce senior-only services).</p>\n<h3>Signature</h3>\n<pre><code class=\"language-text\">age_in_years(date_str, fmt=None) -&gt; int\n</code></pre>\n<h3>Parameters</h3>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>date_str</code></td>\n<td><code>str</code></td>\n<td>Yes</td>\n</tr>\n<tr>\n<td><code>fmt</code></td>\n<td><code>str</code> or <code>None</code></td>\n<td>No</td>\n</tr>\n</tbody></table>\n<ul>\n<li><strong><code>date_str</code></strong> — A date string representing the person&#39;s date of birth.\nISO 8601 format (<code>YYYY-MM-DD</code>) is tried first; if that fails,\n<code>dateutil.parser</code> is used as a fallback, accepting most common\nhuman-readable formats (e.g. <code>&quot;Jan 1, 1990&quot;</code>, <code>&quot;01/01/1990&quot;</code>).</li>\n<li><strong><code>fmt</code></strong> — An explicit\n<a href=\"https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime\"><code>strptime</code></a>\nformat string (e.g. <code>&quot;%d/%m/%Y&quot;</code>). When supplied, no automatic parsing\nis attempted.</li>\n</ul>\n<h3>Returns</h3>\n<p><code>int</code> — The person&#39;s age in complete years (fractional years are truncated,\nnot rounded).</p>\n<h3>Raises</h3>\n<p><code>ValueError</code> — If <code>date_str</code> cannot be parsed (either by ISO 8601, the\nsupplied <code>fmt</code>, or <code>dateutil</code>). At runtime this causes the rule to\n<strong>fail open</strong>.</p>\n<h3>Examples</h3>\n<pre><code class=\"language-python\"># Block patrons under 18 (field returned verbatim from the remote patron_information call)\nage_in_years({polaris_patron_birthdate}) &lt; 18\n\n# Block patrons under 18 using an explicit strptime format\nage_in_years({dob_field}, &quot;%d/%m/%Y&quot;) &lt; 18\n\n# Block patrons aged 65 or over (e.g. senior-only restriction)\nage_in_years({polaris_patron_birthdate}) &gt;= 65\n</code></pre>\n<hr>\n<h2><code>int</code></h2>\n<p>Converts a value to a Python <code>int</code>. Useful when the remote\npatron_information call returns a numeric field as a string (a common\noccurrence) and you need to compare it numerically rather than\nlexicographically.</p>\n<h3>Signature</h3>\n<pre><code class=\"language-text\">int(value) -&gt; int\n</code></pre>\n<h3>Parameters</h3>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>value</code></td>\n<td><code>Any</code></td>\n<td>Yes</td>\n</tr>\n</tbody></table>\n<ul>\n<li><strong><code>value</code></strong> — The value to convert. Typically a string such as <code>&quot;3&quot;</code> or\na float such as <code>2.9</code>. Any value accepted by Python&#39;s built-in <code>int()</code> is\nvalid. Passing a non-numeric string (e.g. <code>&quot;adult&quot;</code>) raises a <code>ValueError</code>\nand causes the rule to <strong>fail open</strong>.</li>\n</ul>\n<h3>Returns</h3>\n<p><code>int</code> — The integer representation of <code>value</code>. Floating-point values are\n<strong>truncated</strong> toward zero (e.g. <code>int(&quot;2.9&quot;)</code> raises <code>ValueError</code>; pass a\nfloat literal or cast via <code>{field} * 1</code> first if you need truncation of\nfloats).</p>\n<h3>Raises</h3>\n<p><code>ValueError</code> — If <code>value</code> cannot be converted to an integer. At runtime\nthis causes the rule to <strong>fail open</strong>.</p>\n<h3>Examples</h3>\n<pre><code class=\"language-python\"># Block patron class codes above 2 (returned as a string by the remote patron_information call)\nint({sipserver_patron_class}) &gt; 2\n\n# Block if a numeric expiry-year field indicates an expired account\nint({expire_year}) &lt; 2025\n</code></pre>\n<hr>\n<h2>Notes</h2>\n<ul>\n<li><p><strong>String methods are available</strong> — methods on Python <code>str</code> values can be\ncalled directly on string-valued placeholders. For example, to check\nwhether a patron identifier starts with a certain prefix:</p>\n<pre><code class=\"language-python\">{patron_identifier}.startswith(&quot;1234&quot;)\n</code></pre>\n</li>\n<li><p><strong>Fail-open behaviour</strong> — any function call that raises an exception\n(e.g. an unparseable date or a non-numeric string passed to <code>int()</code>)\ncauses the rule to be <strong>skipped</strong> at runtime (the patron is not blocked\nby that rule) and the rule to be <strong>accepted</strong> at admin-save time (with\na warning in the Admin UI that there is a problem with the rule). Write\ntest rules carefully using representative patron data before enabling\nthem in production.</p>\n</li>\n<li><p><strong>No other builtins</strong> — Python builtins such as <code>len</code>, <code>str</code>, <code>float</code>,\n<code>abs</code>, and <code>round</code> are <strong>not</strong> available. If you need additional\nfunctions, request them via the standard feature-request process so they\ncan be reviewed and added to <code>DEFAULT_ALLOWED_FUNCTIONS</code> in\n<code>rule_engine.py</code>.</p>\n</li>\n<li><p><strong>Placeholder syntax</strong> — field values from the remote patron_information\ncall are referenced as <code>{field_name}</code>. All fields returned by the\n<code>patron_information</code> command are available, plus the normalised <code>{fines}</code>\nkey (a <code>float</code> derived from <code>fee_amount</code>).</p>\n</li>\n</ul>\n" = '<h1>Patron Blocking Rules — Allowed Functions</h1>\n<p>Patron blocking rule expressions are evaluated by a locked-down\n<a href="https://github.com/danthedeckie/simpleeval">simpleeval</a> sandbox.\nOnly the functions listed below may be called inside a rule expression.\nAny reference to an unlisted function causes the rule to <strong>fail open</strong>\n(the patron is not blocked at runtime; the rule is accepted at admin-save time,\nthough there is feedback in the Admin UI warning the user that there is a problem\nwith the rule).</p>\n<hr>\n<h2><code>age_in_years</code></h2>\n<p>Calculates the age of a person in <strong>whole years</strong> from a date string.\nUse this to write rules that gate access by age (e.g. block minors or\nenforce senior-only services).</p>\n<h3>Signature</h3>\n<pre><code class="language-text">age_in_years(date_str, fmt=None) -&gt; int\n</code></pre>\n<h3>Parameters</h3>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>date_str</code></td>\n<td><code>str</code></td>\n<td>Yes</td>\n</tr>\n<tr>\n<td><code>fmt</code></td>\n<td><code>str</code> or <code>None</code></td>\n<td>No</td>\n</tr>\n</tbody></table>\n<ul>\n<li><strong><code>date_str</code></strong> — A date string representing the person&#39;s date of birth.\nISO 8601 format (<code>YYYY-MM-DD</code>) is tried first; if that fails,\n<code>dateutil.parser</code> is used as a fallback, accepting most common\nhuman-readable formats (e.g. <code>&quot;Jan 1, 1990&quot;</code>, <code>&quot;01/01/1990&quot;</code>).</li>\n<li><strong><code>fmt</code></strong> — An explicit\n<a href="https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime"><code>strptime</code></a>\nformat string (e.g. <code>&quot;%d/%m/%Y&quot;</code>). When supplied, no automatic parsing\nis attempted.</li>\n</ul>\n<h3>Returns</h3>\n<p><code>int</code> — The person&#39;s age in complete years (fractional years are truncated,\nnot rounded).</p>\n<h3>Raises</h3>\n<p><code>ValueError</code> — If <code>date_str</code> cannot be parsed (either by ISO 8601, the\nsupplied <code>fmt</code>, or <code>dateutil</code>). At runtime this causes the rule to\n<strong>fail open</strong>.</p>\n<h3>Examples</h3>\n<pre><code class="language-python"># Block patrons under 18 (field returned verbatim from the remote patron_information call)\nage_in_years({polaris_patron_birthdate}) &lt; 18\n\n# Block patrons under 18 using an explicit strptime format\nage_in_years({dob_field}, &quot;%d/%m/%Y&quot;) &lt; 18\n\n# Block patrons aged 65 or over (e.g. senior-only restriction)\nage_in_years({polaris_patron_birthdate}) &gt;= 65\n</code></pre>\n<hr>\n<h2><code>int</code></h2>\n<p>Converts a value to a Python <code>int</code>. Useful when the remote\npatron_information call returns a numeric field as a string (a common\noccurrence) and you need to compare it numerically rather than\nlexicographically.</p>\n<h3>Signature</h3>\n<pre><code class="language-text">int(value) -&gt; int\n</code></pre>\n<h3>Parameters</h3>\n<table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Type</th>\n<th>Required</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>value</code></td>\n<td><code>Any</code></td>\n<td>Yes</td>\n</tr>\n</tbody></table>\n<ul>\n<li><strong><code>value</code></strong> — The value to convert. Typically a string such as <code>&quot;3&quot;</code> or\na float such as <code>2.9</code>. Any value accepted by Python&#39;s built-in <code>int()</code> is\nvalid. Passing a non-numeric string (e.g. <code>&quot;adult&quot;</code>) raises a <code>ValueError</code>\nand causes the rule to <strong>fail open</strong>.</li>\n</ul>\n<h3>Returns</h3>\n<p><code>int</code> — The integer representation of <code>value</code>. Floating-point values are\n<strong>truncated</strong> toward zero (e.g. <code>int(&quot;2.9&quot;)</code> raises <code>ValueError</code>; pass a\nfloat literal or cast via <code>{field} * 1</code> first if you need truncation of\nfloats).</p>\n<h3>Raises</h3>\n<p><code>ValueError</code> — If <code>value</code> cannot be converted to an integer. At runtime\nthis causes the rule to <strong>fail open</strong>.</p>\n<h3>Examples</h3>\n<pre><code class="language-python"># Block patron class codes above 2 (returned as a string by the remote patron_information call)\nint({sipserver_patron_class}) &gt; 2\n\n# Block if a numeric expiry-year field indicates an expired account\nint({expire_year}) &lt; 2025\n</code></pre>\n<hr>\n<h2>Notes</h2>\n<ul>\n<li><p><strong>String methods are available</strong> — methods on Python <code>str</code> values can be\ncalled directly on string-valued placeholders. For example, to check\nwhether a patron identifier starts with a certain prefix:</p>\n<pre><code class="language-python">{patron_identifier}.startswith(&quot;1234&quot;)\n</code></pre>\n</li>\n<li><p><strong>Fail-open behaviour</strong> — any function call that raises an exception\n(e.g. an unparseable date or a non-numeric string passed to <code>int()</code>)\ncauses the rule to be <strong>skipped</strong> at runtime (the patron is not blocked\nby that rule) and the rule to be <strong>accepted</strong> at admin-save time (with\na warning in the Admin UI that there is a problem with the rule). Write\ntest rules carefully using representative patron data before enabling\nthem in production.</p>\n</li>\n<li><p><strong>No other builtins</strong> — Python builtins such as <code>len</code>, <code>str</code>, <code>float</code>,\n<code>abs</code>, and <code>round</code> are <strong>not</strong> available. If you need additional\nfunctions, request them via the standard feature-request process so they\ncan be reviewed and added to <code>DEFAULT_ALLOWED_FUNCTIONS</code> in\n<code>rule_engine.py</code>.</p>\n</li>\n<li><p><strong>Placeholder syntax</strong> — field values from the remote patron_information\ncall are referenced as <code>{field_name}</code>. All fields returned by the\n<code>patron_information</code> command are available, plus the normalised <code>{fines}</code>\nkey (a <code>float</code> derived from <code>fee_amount</code>).</p>\n</li>\n</ul>\n'