diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b29d51ee..cca23aa4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.12 cache: 'pip' @@ -87,7 +87,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.12 cache: 'pip' @@ -125,7 +125,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.12 cache: 'pip' diff --git a/.gitignore b/.gitignore index c6e5710e7..0c817241e 100644 --- a/.gitignore +++ b/.gitignore @@ -269,6 +269,9 @@ session-replays/ .mcp.json .codex +# Qwen Code per-session state (developer-specific permission grants) +.qwen/ + # Codesight autogenerated context maps. Regenerated by the pre-commit # `post-merge` / `post-rewrite` hooks (`pre-commit install` wires them up # automatically — see `default_install_hook_types` in diff --git a/CLAUDE.md b/CLAUDE.md index 2f4a35543..7180fd457 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,7 @@ Major architectural decisions are recorded in [`docs/adr/`](docs/adr/README.md). - **0020** — Backend owns data, calculations, and external systems; frontend owns presentation. The boundary is the kind of value, not the layer of code. - **0021** — Frontend reads/writes the API only via the generated client; raw `fetch`/`axios` is forbidden. - **0032** — Less code is better: prefer a maintained library over a homegrown implementation. Writing your own for something a library provides needs an explicit, recorded reason it isn't a library. +- **0033** — A version constraint records the newest version that passed testing, never a known incompatibility. Release sweeps ignore the declared bounds and re-test at latest; a version stays behind only when upstream metadata pins it or a gate fails. CLAUDE.md is the operational layer (session behaviour, code-style gotchas, architecture facts). ADRs explain *why*. @@ -153,7 +154,7 @@ Ratchet rules: - ADR 0028: type annotations are data contracts. Touched code must improve mypy by preserving or tightening the real contract; no `Any`, fake `| None`, broad unions, casts, or ignores as checker shortcuts. - ADR 0028 also treats broad `object`, fallback-style `dict.get()`, `hasattr()` contract probes, and happy-case-first branching as soft smells; prefer named types, explicit contracts, direct access after validation, and bad-case guards. - If a touched type becomes hard to read, introduce a named type (`dataclass`, `TypedDict`, protocol, or alias) instead of repeating a complex inline annotation. -- After fixing baselined errors, run `poetry run mypy apps/ docketworks/ | poetry run mypy-baseline sync` and commit the shrunken baseline in the same PR. +- After fixing baselined errors, run `poetry run mypy apps/ docketworks/ | poetry run mypy-baseline sync --sort-baseline` and commit the shrunken baseline in the same PR. `--sort-baseline` keeps the file in canonical order so the diff shows only real add/remove, not mypy's output-order reshuffling. - `# type: ignore[code]` requires the specific error code and a justification comment on the same line. ### Defensive programming @@ -192,12 +193,13 @@ At the HTTP boundary, read the persisted id with `app_error_for(exc)` to include ## Environment Configuration -See `.env.example` for required environment variables. Key integrations: Xero API, Dropbox, PostgreSQL. Frontend tooling reads `APP_DOMAIN` from the backend `.env` at `../.env` and derives URLs from it (see ADR 0008's Consequences). Deploy uses `scripts/server/deploy.sh` (per-instance `-`); it also runs on boot via systemd so a cold machine catches up to `production`. Servers run the `production` branch by default; `main` is the integration branch — never deployed to production, but deployed to UAT as a release candidate via `deploy.sh --ref` / `instance.sh create --ref` (ADR 0029). +See `.env.example` for required environment variables. Key integrations: Xero API, Dropbox, PostgreSQL. Frontend tooling reads `APP_DOMAIN` from the backend `.env` at `../.env` and derives URLs from it (see ADR 0008's Consequences). Deploy uses `scripts/server/deploy.sh` (per-instance `-`). Testing and UAT servers typically track `main`; production servers typically track `production`. After UAT verification, a release PR promotes `main` to `production` (ADR 0029). ## Migration Management - Keep migrations small and reviewable; include forward and (where feasible) reverse logic. - Prefer schema changes over code workarounds that mask data shape issues. +- A nullable column says "unset" as NULL and nothing else — no `""`, no sentinel choice (`UNSPECIFIED`, `N/A`). A `CHECK (col <> '')` constraint enforces it, since the admin, management commands and Xero sync all bypass serializer validation. Serializers on such columns set `allow_blank=False` so bad input is a 400, not an `IntegrityError`. ## Critical Architecture Guidelines diff --git a/apps/accounting/migrations/0004_forbid_blank_text.py b/apps/accounting/migrations/0004_forbid_blank_text.py new file mode 100644 index 000000000..9e0d91f8d --- /dev/null +++ b/apps/accounting/migrations/0004_forbid_blank_text.py @@ -0,0 +1,55 @@ +"""Forbid empty strings in nullable text columns (accounting). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "accounting_bill": [ + "xero_tenant_id" + ], + "accounting_creditnote": [ + "xero_tenant_id" + ], + "accounting_invoice": [ + "online_url", + "xero_tenant_id" + ], + "accounting_quote": [ + "number", + "online_url", + "xero_tenant_id" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("accounting", "0003_rename_client_company"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/accounting/serializers/rdti_spend_serializers.py b/apps/accounting/serializers/rdti_spend_serializers.py index 5953fe968..3b35f2150 100644 --- a/apps/accounting/serializers/rdti_spend_serializers.py +++ b/apps/accounting/serializers/rdti_spend_serializers.py @@ -6,6 +6,8 @@ from rest_framework import serializers +from apps.job.enums import RDTIType + class RDTISpendQuerySerializer(serializers.Serializer[Any]): """Validates query parameters for the RDTI spend report.""" @@ -22,7 +24,7 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: class RDTISpendCategorySummarySerializer(serializers.Serializer[Any]): """Summary data for a single RDTI classification category.""" - rdti_type = serializers.CharField() + rdti_type = serializers.ChoiceField(choices=RDTIType.choices) label = serializers.CharField() hours = serializers.FloatField() cost = serializers.FloatField() @@ -37,7 +39,7 @@ class RDTISpendJobDetailSerializer(serializers.Serializer[Any]): job_number = serializers.IntegerField() job_name = serializers.CharField() company_name = serializers.CharField() - rdti_type = serializers.CharField() + rdti_type = serializers.ChoiceField(choices=RDTIType.choices) hours = serializers.FloatField() cost = serializers.FloatField() revenue = serializers.FloatField() diff --git a/apps/accounting/services/__init__.py b/apps/accounting/services/__init__.py index bc430acd7..910d6dedc 100644 --- a/apps/accounting/services/__init__.py +++ b/apps/accounting/services/__init__.py @@ -6,11 +6,18 @@ if apps.ready: from .core import JobAgingService, KPIService, StaffPerformanceService + from .finish_job_summary import ( + FinishJobSummary, + build_finish_job_summary, + get_outstanding_invoiced_incl_tax, + ) from .invoice_calculation import ( InvoiceCalculationError, InvoiceCalculationResult, + JobInvoicingBasis, calculate_invoice_amount, get_job_for_invoice_calculation, + get_job_invoicing_basis, get_prior_valid_invoice_total, ) from .payroll_reconciliation_service import PayrollReconciliationService @@ -22,16 +29,21 @@ pass __all__ = [ + "FinishJobSummary", "InvoiceCalculationError", "InvoiceCalculationResult", "JobAgingService", + "JobInvoicingBasis", "KPIService", "PayrollReconciliationService", "RDTISpendService", "SalesPipelineService", "StaffPerformanceService", "WIPService", + "build_finish_job_summary", "calculate_invoice_amount", "get_job_for_invoice_calculation", + "get_job_invoicing_basis", + "get_outstanding_invoiced_incl_tax", "get_prior_valid_invoice_total", ] diff --git a/apps/accounting/services/finish_job_summary.py b/apps/accounting/services/finish_job_summary.py new file mode 100644 index 000000000..959f3948d --- /dev/null +++ b/apps/accounting/services/finish_job_summary.py @@ -0,0 +1,80 @@ +"""Authoritative customer balance for the Finish Job workspace. + +Answers the counter question — what does this customer need to pay, including +tax — as decimal currency values, so the frontend only formats what it is given +(ADR 0020). + +Adds nothing but the tax and outstanding-balance arithmetic: the job's value, its +price cap and the valid-invoice statuses all come from ``invoice_calculation``, +so the amount a user can invoice and the amount they are shown cannot drift +apart. +""" + +from dataclasses import dataclass +from decimal import ROUND_HALF_UP, Decimal + +from django.db.models import Sum +from django.db.models.functions import Coalesce + +from apps.accounting.models.invoice import Invoice +from apps.accounting.services.invoice_calculation import ( + INVOICE_VALID_STATUSES, + get_job_invoicing_basis, + get_prior_valid_invoice_total, +) +from apps.job.models import Job +from apps.workflow.models import CompanyDefaults + +CENT = Decimal("0.01") + + +@dataclass(frozen=True) +class FinishJobSummary: + """Every currency value the Finish Job workspace displays.""" + + job_value_excl_gst: Decimal + valid_invoiced_excl_gst: Decimal + outstanding_invoiced_incl_gst: Decimal + remaining_to_invoice_excl_gst: Decimal + remaining_gst: Decimal + remaining_to_invoice_incl_gst: Decimal + total_to_pay_incl_gst: Decimal + over_invoiced_excl_gst: Decimal + + +def get_outstanding_invoiced_incl_tax(job: Job) -> Decimal: + """What Xero still expects to be paid on this job's valid invoices.""" + return Decimal( + Invoice.objects.filter( + job_id=job.id, status__in=INVOICE_VALID_STATUSES + ).aggregate(total=Coalesce(Sum("amount_due"), Decimal("0")))["total"] + ) + + +def build_finish_job_summary(job: Job) -> FinishJobSummary: + job_value = get_job_invoicing_basis(job).target_total.quantize( + CENT, rounding=ROUND_HALF_UP + ) + invoiced = get_prior_valid_invoice_total(job) + outstanding = get_outstanding_invoiced_incl_tax(job) + + # A job invoiced beyond its value has nothing left to invoice; the excess is + # reported separately for resolution in Xero rather than netted off, which + # would hide it behind a smaller remaining balance. + remaining_excl = max(job_value - invoiced, Decimal("0")) + over_invoiced = max(invoiced - job_value, Decimal("0")) + + gst_rate = CompanyDefaults.get_solo().gst_rate + remaining_gst = (remaining_excl * gst_rate).quantize(CENT, rounding=ROUND_HALF_UP) + remaining_incl = remaining_excl + remaining_gst + + return FinishJobSummary( + job_value_excl_gst=job_value, + valid_invoiced_excl_gst=invoiced, + outstanding_invoiced_incl_gst=outstanding, + remaining_to_invoice_excl_gst=remaining_excl, + remaining_gst=remaining_gst, + remaining_to_invoice_incl_gst=remaining_incl, + total_to_pay_incl_gst=outstanding + remaining_incl, + over_invoiced_excl_gst=over_invoiced, + ) diff --git a/apps/accounting/services/invoice_calculation.py b/apps/accounting/services/invoice_calculation.py index ac783abf0..44e9320a8 100644 --- a/apps/accounting/services/invoice_calculation.py +++ b/apps/accounting/services/invoice_calculation.py @@ -31,6 +31,38 @@ class InvoiceCalculationResult: calculated_amount: Decimal = Decimal("0") +@dataclass(frozen=True) +class JobInvoicingBasis: + """What a job is worth excluding tax, and which cost set says so.""" + + basis: str + target_total: Decimal + + +def get_job_invoicing_basis(job: Job) -> JobInvoicingBasis: + """The complete value of a job excluding tax. + + The single place a job's value is derived: fixed-price work is worth its + quote, T&M work its actual revenue limited by any price cap. Everything that + needs a job's value — invoice calculation, the Finish Job balance, + ``job_service.get_job_total_value`` — reads it from here, so the three cannot + disagree about what a job is worth. + """ + if job.pricing_methodology == "fixed_price": + return JobInvoicingBasis( + basis="quote", target_total=Decimal(str(job.latest_quote.total_revenue)) + ) + + actual_revenue = Decimal(str(job.latest_actual.total_revenue)) + if job.price_cap is None: + return JobInvoicingBasis(basis="actual_revenue", target_total=actual_revenue) + + return JobInvoicingBasis( + basis="actual_revenue", + target_total=min(actual_revenue, Decimal(str(job.price_cap))), + ) + + def get_prior_valid_invoice_total(job: Job) -> Decimal: return Decimal( Invoice.objects.filter( @@ -40,9 +72,13 @@ def get_prior_valid_invoice_total(job: Job) -> Decimal: def get_job_for_invoice_calculation(job_id: UUID) -> Job: - job = Job.objects.select_related("company", "latest_quote", "latest_actual").get( - id=job_id - ) + """Load a job with the cost set its value is measured against. + + Only the basis cost set is fetched: loading both eagerly pulls one that is + never read, which the n+1 guard reports. Its cost lines are prefetched + because ``total_revenue`` sums them. + """ + job = Job.objects.select_related("company").get(id=job_id) if job.pricing_methodology == "fixed_price": prefetch_related_objects([job], "latest_quote__cost_lines") @@ -59,27 +95,26 @@ def calculate_invoice_amount( amount: Decimal | None = None, ) -> InvoiceCalculationResult: prior_invoiced = get_prior_valid_invoice_total(job) + job_basis = get_job_invoicing_basis(job) + # Which modes are legal is a different question from what the job is worth, + # so it keeps its own branch; the handlers below are given the value. if job.pricing_methodology == "fixed_price": - return _calculate_fixed_price(job, mode, prior_invoiced, percent, amount) + return _calculate_fixed_price(job_basis, mode, prior_invoiced, percent, amount) else: - return _calculate_time_materials(job, mode, prior_invoiced, percent, amount) + return _calculate_time_materials( + job_basis, mode, prior_invoiced, percent, amount + ) def _calculate_fixed_price( - job: Job, + job_basis: JobInvoicingBasis, mode: str, prior_invoiced: Decimal, percent: Decimal | None, amount: Decimal | None, ) -> InvoiceCalculationResult: - quote = job.latest_quote - if not quote: - raise InvoiceCalculationError( - "Fixed-price job has no quote to invoice against." - ) - target_total = Decimal(str(quote.total_revenue)) - target_basis = "quote" + target_total = job_basis.target_total if mode == "invoice_full": calculated = target_total - prior_invoiced @@ -108,7 +143,7 @@ def _calculate_fixed_price( return InvoiceCalculationResult( mode=mode, - target_basis=target_basis, + target_basis=job_basis.basis, target_total=target_total, prior_invoiced_total=prior_invoiced, requested_percent=percent, @@ -118,23 +153,13 @@ def _calculate_fixed_price( def _calculate_time_materials( - job: Job, + job_basis: JobInvoicingBasis, mode: str, prior_invoiced: Decimal, percent: Decimal | None, amount: Decimal | None, ) -> InvoiceCalculationResult: - actual = job.latest_actual - if not actual: - raise InvoiceCalculationError( - "T&M job has no actual cost set to invoice against." - ) - target_total = Decimal(str(actual.total_revenue)) - - if job.price_cap is not None: - target_total = min(target_total, Decimal(str(job.price_cap))) - - target_basis = "actual_revenue" + target_total = job_basis.target_total if mode == "invoice_costs_to_date": calculated = target_total - prior_invoiced @@ -158,7 +183,7 @@ def _calculate_time_materials( return InvoiceCalculationResult( mode=mode, - target_basis=target_basis, + target_basis=job_basis.basis, target_total=target_total, prior_invoiced_total=prior_invoiced, requested_percent=percent, diff --git a/apps/accounting/services/payroll_reconciliation_service.py b/apps/accounting/services/payroll_reconciliation_service.py index 2e70c4058..e6145b817 100644 --- a/apps/accounting/services/payroll_reconciliation_service.py +++ b/apps/accounting/services/payroll_reconciliation_service.py @@ -139,12 +139,7 @@ def _get_monday(d: date) -> date: def _build_staff_xero_map() -> dict[str, Staff]: """Map xero_employee_id (str) -> Staff object.""" - return { - s.xero_user_id: s - for s in Staff.objects.exclude(xero_user_id__isnull=True).exclude( - xero_user_id="" - ) - } + return {s.xero_user_id: s for s in Staff.objects.exclude(xero_user_id__isnull=True)} def _get_xero_week_data( diff --git a/apps/accounting/tests/test_core_nplusone.py b/apps/accounting/tests/test_core_nplusone.py index 3f0c3faaa..8ec705e5d 100644 --- a/apps/accounting/tests/test_core_nplusone.py +++ b/apps/accounting/tests/test_core_nplusone.py @@ -22,7 +22,7 @@ class AccountingCoreQueryTests(BaseTestCase): rows and a fixed query budget for the report boundary. """ - def setUp(self): + def setUp(self) -> None: super().setUp() self.target_date = date(2026, 5, 22) self.company = Company.objects.create( @@ -75,7 +75,7 @@ def _create_time_line(self, job: Job, staff: Staff, hours: str = "2.000"): entry_seq=1, ) - def test_kpi_job_breakdown_preloads_job_client(self): + def test_kpi_job_breakdown_preloads_job_client(self) -> None: """Job breakdown must not fetch each job's company inside the loop. This catches removing ``select_related("cost_set__job__company")`` by @@ -100,7 +100,7 @@ def test_kpi_job_breakdown_preloads_job_client(self): self.assertEqual(breakdown[0]["company_name"], "Accounting Nplusone Company") - def test_staff_performance_groups_prefetched_cost_lines_by_staff(self): + def test_staff_performance_groups_prefetched_cost_lines_by_staff(self) -> None: """Staff performance must group prefetched lines without per-staff queries. This catches metric code that looks up CostLine/job/company data inside diff --git a/apps/accounting/tests/test_finish_job_summary.py b/apps/accounting/tests/test_finish_job_summary.py new file mode 100644 index 000000000..25667f442 --- /dev/null +++ b/apps/accounting/tests/test_finish_job_summary.py @@ -0,0 +1,234 @@ +"""Tests for the Finish Job customer balance. + +The counter situations from KAN-323 are all the same arithmetic over different +invoice states, so they are one table. The cases that are not just arithmetic — +the price cap, excluded statuses, over-invoicing and GST rounding — get their own +tests. +""" + +import uuid +from datetime import date +from decimal import Decimal + +from django.utils import timezone + +from apps.accounting.models.invoice import Invoice +from apps.accounting.services.finish_job_summary import build_finish_job_summary +from apps.company.models import Company +from apps.job.models import Job +from apps.job.models.costing import CostSet +from apps.testing import BaseTestCase +from apps.workflow.models import CompanyDefaults + + +class TestFinishJobSummary(BaseTestCase): + def setUp(self) -> None: + self.client_obj = Company.objects.create( + name="Test Company", + xero_last_modified=timezone.now(), + ) + defaults = CompanyDefaults.get_solo() + defaults.gst_rate = Decimal("0.1500") + defaults.save() + + def _job(self, pricing_methodology: str, revenue: Decimal) -> Job: + job = Job( + company=self.client_obj, + name="Test Job", + pricing_methodology=pricing_methodology, + ) + job.save(staff=self.test_staff) + basis = ( + job.latest_quote + if pricing_methodology == "fixed_price" + else job.latest_actual + ) + self._add_revenue_line(basis, revenue) + return job + + def _add_revenue_line(self, cost_set: CostSet, revenue: Decimal) -> None: + from apps.job.models.costing import CostLine + + CostLine.objects.create( + cost_set=cost_set, + kind="adjust", + desc="Test line", + quantity=Decimal("1.000"), + unit_cost=Decimal("0.00"), + unit_rev=revenue, + accounting_date=date.today(), + ) + + def _invoice( + self, + job: Job, + amount: Decimal, + amount_due: Decimal = Decimal("0.00"), + status: str = "AUTHORISED", + ) -> Invoice: + return Invoice.objects.create( + job=job, + company=self.client_obj, + xero_id=uuid.uuid4(), + number=f"INV-{uuid.uuid4().hex[:8]}", + status=status, + total_excl_tax=amount, + tax=(amount * Decimal("0.15")).quantize(Decimal("0.01")), + total_incl_tax=amount * Decimal("1.15"), + amount_due=amount_due, + date=date.today(), + xero_last_modified=timezone.now(), + raw_json={}, + ) + + def test_counter_situations(self) -> None: + """Every KAN-323 acceptance scenario, on a $1000 fixed-price job.""" + cases = [ + # label, invoices as (excl_tax, amount_due, status), expected total to pay + ("nothing invoiced", [], Decimal("1150.00")), + ( + "paid in advance", + [(Decimal("1000"), Decimal("0.00"), "PAID")], + Decimal("0.00"), + ), + ( + "unpaid advance invoice", + [(Decimal("1000"), Decimal("1150.00"), "AUTHORISED")], + Decimal("1150.00"), + ), + ( + "paid deposit, balance uninvoiced", + [(Decimal("400"), Decimal("0.00"), "PAID")], + Decimal("690.00"), + ), + ( + "unpaid progress invoice plus uninvoiced balance", + [(Decimal("400"), Decimal("460.00"), "AUTHORISED")], + Decimal("1150.00"), + ), + ( + "partly paid invoice", + [(Decimal("1000"), Decimal("150.00"), "AUTHORISED")], + Decimal("150.00"), + ), + ] + + for label, invoices, expected in cases: + with self.subTest(label): + job = self._job("fixed_price", Decimal("1000")) + for amount, due, status in invoices: + self._invoice(job, amount, amount_due=due, status=status) + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.total_to_pay_incl_gst, expected) + + def test_time_materials_value_is_limited_by_price_cap(self) -> None: + job = self._job("time_materials", Decimal("5000")) + job.price_cap = Decimal("3000.00") + job.save(staff=self.test_staff) + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.job_value_excl_gst, Decimal("3000.00")) + self.assertEqual(summary.remaining_to_invoice_excl_gst, Decimal("3000.00")) + + def test_voided_and_deleted_invoices_do_not_count(self) -> None: + job = self._job("fixed_price", Decimal("1000")) + self._invoice(job, Decimal("1000"), Decimal("1150.00"), "VOIDED") + self._invoice(job, Decimal("1000"), Decimal("1150.00"), "DELETED") + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.valid_invoiced_excl_gst, Decimal("0.00")) + self.assertEqual(summary.outstanding_invoiced_incl_gst, Decimal("0.00")) + self.assertEqual(summary.total_to_pay_incl_gst, Decimal("1150.00")) + + def test_over_invoicing_is_reported_without_a_negative_remainder(self) -> None: + """T&M actuals can land below what was already invoiced.""" + job = self._job("time_materials", Decimal("800")) + self._invoice(job, Decimal("1000"), Decimal("0.00"), "PAID") + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.over_invoiced_excl_gst, Decimal("200.00")) + self.assertEqual(summary.remaining_to_invoice_excl_gst, Decimal("0.00")) + self.assertEqual(summary.total_to_pay_incl_gst, Decimal("0.00")) + + def test_gst_rounds_to_the_nearest_cent(self) -> None: + """$333.33 x 15% = $49.9995, which must present as $50.00.""" + job = self._job("time_materials", Decimal("333.33")) + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.remaining_gst, Decimal("50.00")) + self.assertEqual(summary.total_to_pay_incl_gst, Decimal("383.33")) + + def test_gst_uses_the_configured_rate(self) -> None: + defaults = CompanyDefaults.get_solo() + defaults.gst_rate = Decimal("0.1250") + defaults.save() + job = self._job("time_materials", Decimal("1000")) + + summary = build_finish_job_summary(job) + + self.assertEqual(summary.remaining_gst, Decimal("125.00")) + self.assertEqual(summary.total_to_pay_incl_gst, Decimal("1125.00")) + + +class TestJobValueIsSharedWithReporting(BaseTestCase): + """get_job_total_value must agree with the invoicing basis. + + It used to read CostSet.summary["rev"], a float mirror maintained by + CostLine._update_cost_set_summary, while invoicing read total_revenue. Both + now come from get_job_invoicing_basis, so a job cannot be reported at one + value and invoiced at another. + """ + + def setUp(self) -> None: + self.client_obj = Company.objects.create( + name="Test Company", + xero_last_modified=timezone.now(), + ) + + def _job_with_revenue(self, revenue: Decimal) -> Job: + from apps.job.models.costing import CostLine + + job = Job( + company=self.client_obj, + name="Test Job", + pricing_methodology="time_materials", + ) + job.save(staff=self.test_staff) + CostLine.objects.create( + cost_set=job.latest_actual, + kind="adjust", + desc="Test line", + quantity=Decimal("1.000"), + unit_cost=Decimal("0.00"), + unit_rev=revenue, + accounting_date=date.today(), + ) + return job + + def test_reported_value_matches_the_invoicing_basis(self) -> None: + from apps.accounting.services.invoice_calculation import ( + get_job_invoicing_basis, + ) + from apps.job.services.job_service import get_job_total_value + + job = self._job_with_revenue(Decimal("1234.56")) + + self.assertEqual( + get_job_total_value(job), get_job_invoicing_basis(job).target_total + ) + + def test_reported_value_respects_the_price_cap(self) -> None: + """The old implementation ignored price_cap and over-reported.""" + from apps.job.services.job_service import get_job_total_value + + job = self._job_with_revenue(Decimal("5000")) + job.price_cap = Decimal("3000.00") + job.save(staff=self.test_staff) + + self.assertEqual(get_job_total_value(job), Decimal("3000.00")) diff --git a/apps/accounting/tests/test_invoice_calculation.py b/apps/accounting/tests/test_invoice_calculation.py index 6cd80aff4..fd0b2c8fa 100644 --- a/apps/accounting/tests/test_invoice_calculation.py +++ b/apps/accounting/tests/test_invoice_calculation.py @@ -17,20 +17,20 @@ ) from apps.company.models import Company from apps.job.models import Job -from apps.job.models.costing import CostLine +from apps.job.models.costing import CostLine, CostSet from apps.testing import BaseTestCase class TestInvoiceCalculation(BaseTestCase): """Tests for calculate_invoice_amount().""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Test Company", xero_last_modified=timezone.now(), ) - def _create_job(self, pricing_methodology="time_materials"): + def _create_job(self, pricing_methodology: str = "time_materials") -> Job: job = Job( company=self.client_obj, name="Test Job", @@ -39,7 +39,7 @@ def _create_job(self, pricing_methodology="time_materials"): job.save(staff=self.test_staff) return job - def _add_revenue_line(self, cost_set, revenue): + def _add_revenue_line(self, cost_set: CostSet, revenue: Decimal) -> None: CostLine.objects.create( cost_set=cost_set, kind="adjust", @@ -50,7 +50,9 @@ def _add_revenue_line(self, cost_set, revenue): accounting_date=date.today(), ) - def _create_invoice(self, job, amount, status="AUTHORISED"): + def _create_invoice( + self, job: Job, amount: Decimal, status: str = "AUTHORISED" + ) -> Invoice: return Invoice.objects.create( job=job, company=self.client_obj, @@ -68,7 +70,7 @@ def _create_invoice(self, job, amount, status="AUTHORISED"): # --- Quoted job: invoice_full --- - def test_quoted_invoice_full_no_prior(self): + def test_quoted_invoice_full_no_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) result = calculate_invoice_amount(job, mode="invoice_full") @@ -77,14 +79,14 @@ def test_quoted_invoice_full_no_prior(self): self.assertEqual(result.target_total, Decimal("5000")) self.assertEqual(result.prior_invoiced_total, Decimal("0")) - def test_quoted_invoice_full_with_prior(self): + def test_quoted_invoice_full_with_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("3000")) result = calculate_invoice_amount(job, mode="invoice_full") self.assertEqual(result.calculated_amount, Decimal("2000")) - def test_quoted_invoice_full_fully_invoiced(self): + def test_quoted_invoice_full_fully_invoiced(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("5000")) @@ -93,7 +95,7 @@ def test_quoted_invoice_full_fully_invoiced(self): # --- Quoted job: invoice_percent --- - def test_quoted_invoice_percent_no_prior(self): + def test_quoted_invoice_percent_no_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) result = calculate_invoice_amount( @@ -101,7 +103,7 @@ def test_quoted_invoice_percent_no_prior(self): ) self.assertEqual(result.calculated_amount, Decimal("2500")) - def test_quoted_invoice_percent_with_prior(self): + def test_quoted_invoice_percent_with_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("1000")) @@ -110,7 +112,7 @@ def test_quoted_invoice_percent_with_prior(self): ) self.assertEqual(result.calculated_amount, Decimal("1500")) - def test_quoted_invoice_percent_missing_percent(self): + def test_quoted_invoice_percent_missing_percent(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) with self.assertRaises(InvoiceCalculationError): @@ -118,7 +120,7 @@ def test_quoted_invoice_percent_missing_percent(self): # --- Quoted job: invoice_amount --- - def test_quoted_invoice_amount_valid(self): + def test_quoted_invoice_amount_valid(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) result = calculate_invoice_amount( @@ -126,7 +128,7 @@ def test_quoted_invoice_amount_valid(self): ) self.assertEqual(result.calculated_amount, Decimal("2000")) - def test_quoted_invoice_amount_exceeds_remaining(self): + def test_quoted_invoice_amount_exceeds_remaining(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("4000")) @@ -135,14 +137,14 @@ def test_quoted_invoice_amount_exceeds_remaining(self): # --- T&M job: invoice_costs_to_date --- - def test_tm_costs_to_date_no_prior(self): + def test_tm_costs_to_date_no_prior(self) -> None: job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("7500")) result = calculate_invoice_amount(job, mode="invoice_costs_to_date") self.assertEqual(result.calculated_amount, Decimal("7500")) self.assertEqual(result.target_basis, "actual_revenue") - def test_tm_costs_to_date_with_prior(self): + def test_tm_costs_to_date_with_prior(self) -> None: job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("7500")) self._create_invoice(job, Decimal("2500")) @@ -151,7 +153,7 @@ def test_tm_costs_to_date_with_prior(self): # --- T&M job: invoice_amount --- - def test_tm_invoice_amount_valid(self): + def test_tm_invoice_amount_valid(self) -> None: job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("7500")) result = calculate_invoice_amount( @@ -161,7 +163,7 @@ def test_tm_invoice_amount_valid(self): # --- T&M job: invalid modes --- - def test_tm_invoice_percent_rejected(self): + def test_tm_invoice_percent_rejected(self) -> None: job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("7500")) with self.assertRaises(InvoiceCalculationError): @@ -169,14 +171,14 @@ def test_tm_invoice_percent_rejected(self): # --- Voided/deleted invoices --- - def test_voided_invoices_excluded_from_prior(self): + def test_voided_invoices_excluded_from_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("3000"), status="VOIDED") result = calculate_invoice_amount(job, mode="invoice_full") self.assertEqual(result.calculated_amount, Decimal("5000")) - def test_deleted_invoices_excluded_from_prior(self): + def test_deleted_invoices_excluded_from_prior(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("3000"), status="DELETED") @@ -185,7 +187,7 @@ def test_deleted_invoices_excluded_from_prior(self): # --- Price cap --- - def test_tm_price_cap_applied(self): + def test_tm_price_cap_applied(self) -> None: job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("10000")) job.price_cap = Decimal("8000") @@ -195,7 +197,7 @@ def test_tm_price_cap_applied(self): # --- Negative/zero rejection --- - def test_zero_invoice_rejected(self): + def test_zero_invoice_rejected(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("0")) with self.assertRaises(InvoiceCalculationError): @@ -203,7 +205,7 @@ def test_zero_invoice_rejected(self): # --- get_prior_valid_invoice_total --- - def test_prior_total_excludes_voided_deleted(self): + def test_prior_total_excludes_voided_deleted(self) -> None: job = self._create_job("fixed_price") self._add_revenue_line(job.latest_quote, Decimal("5000")) self._create_invoice(job, Decimal("1000"), status="AUTHORISED") @@ -212,7 +214,7 @@ def test_prior_total_excludes_voided_deleted(self): total = get_prior_valid_invoice_total(job) self.assertEqual(total, Decimal("1000")) - def test_preloaded_job_calculates_without_lazy_loading_cost_lines(self): + def test_preloaded_job_calculates_without_lazy_loading_cost_lines(self) -> None: """Xero invoice creation preloads CostSet lines before calculation.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("250")) diff --git a/apps/accounting/tests/test_invoice_models.py b/apps/accounting/tests/test_invoice_models.py index 349c49e11..4a94b68d6 100644 --- a/apps/accounting/tests/test_invoice_models.py +++ b/apps/accounting/tests/test_invoice_models.py @@ -10,7 +10,7 @@ class InvoiceModelTests(BaseTestCase): - def test_total_amount_sums_reverse_line_items(self): + def test_total_amount_sums_reverse_line_items(self) -> None: """Guards against ``total_amount`` returning a stale or incorrect value when line items are added or modified — a bug here silently corrupts invoice totals displayed to users and synced to Xero. diff --git a/apps/accounting/tests/test_sales_pipeline_api.py b/apps/accounting/tests/test_sales_pipeline_api.py index 208fbbf1a..9d5232c1c 100644 --- a/apps/accounting/tests/test_sales_pipeline_api.py +++ b/apps/accounting/tests/test_sales_pipeline_api.py @@ -42,7 +42,7 @@ def setUp(self) -> None: def _get(self, params: dict | None = None): return self.client.get(self.url, data=params or {}) - def test_valid_request_returns_all_sections(self): + def test_valid_request_returns_all_sections(self) -> None: resp = self._get( { "start_date": "2026-01-01", @@ -67,37 +67,37 @@ def test_valid_request_returns_all_sections(self): self.assertEqual(len(body["trend"]["weeks"]), 4) self.assertEqual(len(body["trend"]["rolling_average"]), 4) - def test_omitted_end_date_defaults_to_today(self): + def test_omitted_end_date_defaults_to_today(self) -> None: today = timezone.localdate() resp = self._get({"start_date": "2026-01-01"}) self.assertEqual(resp.status_code, status.HTTP_200_OK, resp.content) body = resp.json() self.assertEqual(body["period"]["end_date"], today.isoformat()) - def test_missing_start_date_returns_400(self): + def test_missing_start_date_returns_400(self) -> None: resp = self._get({"end_date": "2026-02-01"}) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) body = resp.json() self.assertIn("details", body) self.assertIn("start_date", body["details"]) - def test_invalid_start_date_returns_400(self): + def test_invalid_start_date_returns_400(self) -> None: resp = self._get({"start_date": "not-a-date"}) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("start_date", resp.json()["details"]) - def test_invalid_end_date_returns_400(self): + def test_invalid_end_date_returns_400(self) -> None: resp = self._get({"start_date": "2026-01-01", "end_date": "2026-13-40"}) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("end_date", resp.json()["details"]) - def test_start_after_end_returns_400(self): + def test_start_after_end_returns_400(self) -> None: resp = self._get({"start_date": "2026-02-10", "end_date": "2026-02-01"}) self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) # ``validate()`` surfaces this on end_date per the serializer. self.assertIn("end_date", resp.json()["details"]) - def test_non_positive_rolling_window_returns_400(self): + def test_non_positive_rolling_window_returns_400(self) -> None: resp = self._get( { "start_date": "2026-01-01", @@ -108,7 +108,7 @@ def test_non_positive_rolling_window_returns_400(self): self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("rolling_window_weeks", resp.json()["details"]) - def test_non_positive_trend_weeks_returns_400(self): + def test_non_positive_trend_weeks_returns_400(self) -> None: resp = self._get( { "start_date": "2026-01-01", @@ -119,7 +119,7 @@ def test_non_positive_trend_weeks_returns_400(self): self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn("trend_weeks", resp.json()["details"]) - def test_unexpected_service_failure_returns_standard_error_shape(self): + def test_unexpected_service_failure_returns_standard_error_shape(self) -> None: """Unhandled service exceptions must be persisted via ``persist_app_error`` and surfaced as a 500 with the standard error payload (``error`` plus ``details.error_id``).""" @@ -136,7 +136,7 @@ def test_unexpected_service_failure_returns_standard_error_shape(self): self.assertIn("details", body) self.assertIn("error_id", body["details"]) - def test_unauthenticated_request_is_rejected(self): + def test_unauthenticated_request_is_rejected(self) -> None: anon = APIClient() resp = anon.get(self.url, data={"start_date": "2026-01-01"}) self.assertIn( @@ -144,7 +144,7 @@ def test_unauthenticated_request_is_rejected(self): (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN), ) - def test_default_rolling_window_and_trend_weeks(self): + def test_default_rolling_window_and_trend_weeks(self) -> None: """When only dates are supplied, the serializer fills in the documented defaults (rolling_window_weeks=4, trend_weeks=13).""" start = timezone.localdate() - timedelta(days=30) diff --git a/apps/accounting/tests/test_sales_pipeline_service.py b/apps/accounting/tests/test_sales_pipeline_service.py index 96b62b3b9..14156f9fa 100644 --- a/apps/accounting/tests/test_sales_pipeline_service.py +++ b/apps/accounting/tests/test_sales_pipeline_service.py @@ -142,7 +142,7 @@ def setUp(self) -> None: self.start = date(2026, 3, 2) # Monday self.end = date(2026, 3, 6) # Friday — 5 working days - def test_status_changed_into_approved_counts(self): + def test_status_changed_into_approved_counts(self) -> None: job = self._make_job( name="J1", company=self.client_obj, created_dt=_nz_dt(date(2026, 1, 5)) ) @@ -160,7 +160,7 @@ def test_status_changed_into_approved_counts(self): # Not direct — went through awaiting_approval. self.assertEqual(rep["scoreboard"]["direct_jobs_count"], 0) - def test_quote_accepted_counts(self): + def test_quote_accepted_counts(self) -> None: job = self._make_job( name="J2", company=self.client_obj, created_dt=_nz_dt(date(2026, 1, 5)) ) @@ -174,7 +174,7 @@ def test_quote_accepted_counts(self): self.assertEqual(rep["scoreboard"]["approved_jobs_count"], 1) self.assertAlmostEqual(rep["scoreboard"]["approved_hours_total"], 4.0) - def test_direct_approved_counts_in_direct_bucket(self): + def test_direct_approved_counts_in_direct_bucket(self) -> None: job = self._make_job( name="J3", company=self.client_obj, created_dt=_nz_dt(date(2026, 1, 5)) ) @@ -189,7 +189,7 @@ def test_direct_approved_counts_in_direct_bucket(self): self.assertEqual(rep["scoreboard"]["direct_jobs_count"], 1) self.assertAlmostEqual(rep["scoreboard"]["direct_hours"], 6.0) - def test_dedupe_approved_then_in_progress_same_period(self): + def test_dedupe_approved_then_in_progress_same_period(self) -> None: job = self._make_job( name="J4", company=self.client_obj, created_dt=_nz_dt(date(2026, 1, 5)) ) @@ -208,7 +208,7 @@ def test_dedupe_approved_then_in_progress_same_period(self): self.assertEqual(rep["scoreboard"]["approved_jobs_count"], 1) self.assertAlmostEqual(rep["scoreboard"]["approved_hours_total"], 10.0) - def test_target_reflects_company_defaults(self): + def test_target_reflects_company_defaults(self) -> None: defaults = CompanyDefaults.get_solo() defaults.daily_approved_hours_target = Decimal("20.00") defaults.save() @@ -219,7 +219,7 @@ def test_target_reflects_company_defaults(self): self.assertAlmostEqual(rep["scoreboard"]["target_hours_for_period"], 100.0) self.assertAlmostEqual(rep["period"]["daily_approved_hours_target"], 20.0) - def test_missing_hours_summary_excludes_and_warns(self): + def test_missing_hours_summary_excludes_and_warns(self) -> None: job = self._make_job( name="No hours", company=self.client_obj, @@ -246,7 +246,7 @@ def setUp(self) -> None: super().setUp() self.client_obj = self._make_client() - def test_replays_to_historical_status_not_live(self): + def test_replays_to_historical_status_not_live(self) -> None: # Created in draft 2026-01-05, moved to awaiting_approval 2026-02-01, # approved 2026-03-15, archived 2026-04-10. As of end_date 2026-02-15 # the historical state is "awaiting_approval". @@ -279,7 +279,7 @@ def test_replays_to_historical_status_not_live(self): rep["pipeline_snapshot"]["awaiting_approval"]["hours_total"], 7.0 ) - def test_days_in_stage_uses_most_recent_entry(self): + def test_days_in_stage_uses_most_recent_entry(self) -> None: end_date = date(2026, 3, 20) # Created in awaiting_approval, bounced back to draft, then back to # awaiting_approval. Days-in-stage measured from the latest entry. @@ -308,7 +308,7 @@ def test_days_in_stage_uses_most_recent_entry(self): self.assertEqual(bucket["count"], 1) self.assertEqual(bucket["jobs"][0]["days_in_stage"], 10) - def test_draft_uses_estimate_summary_awaiting_uses_quote(self): + def test_draft_uses_estimate_summary_awaiting_uses_quote(self) -> None: d_job = self._make_job( name="DraftJ", company=self.client_obj, created_dt=_nz_dt(date(2026, 2, 1)) ) @@ -333,7 +333,7 @@ def test_draft_uses_estimate_summary_awaiting_uses_quote(self): rep["pipeline_snapshot"]["awaiting_approval"]["hours_total"], 4.5 ) - def test_narrowed_fetch_preserves_historical_replay(self): + def test_narrowed_fetch_preserves_historical_replay(self) -> None: """A job created long before the reporting window whose transitions leave it in a pipeline stage at ``end_date`` must still surface in the snapshot. Tier 1 narrows the fetch window to save work, so this @@ -364,7 +364,7 @@ def test_narrowed_fetch_preserves_historical_replay(self): self.assertEqual(bucket["count"], 1) self.assertAlmostEqual(bucket["hours_total"], 9.0) - def test_narrowed_fetch_applies_lower_bound_for_in_window_query(self): + def test_narrowed_fetch_applies_lower_bound_for_in_window_query(self) -> None: """The main events query must carry a ``timestamp >=`` filter so we aren't hauling years of history for a short report window. The pre-window query that backfills pipeline-stage jobs is allowed to @@ -411,7 +411,7 @@ def test_narrowed_fetch_applies_lower_bound_for_in_window_query(self): "fetch narrowing regressed. Queries:\n" + "\n".join(jobevent_selects), ) - def test_missing_creation_anchor_excludes_and_warns(self): + def test_missing_creation_anchor_excludes_and_warns(self) -> None: # Build a job and then delete its job_created event. job = self._make_job( name="Anchorless", @@ -438,7 +438,7 @@ def setUp(self) -> None: super().setUp() self.client_obj = self._make_client() - def test_median_p80_sample_size(self): + def test_median_p80_sample_size(self) -> None: """Three approvals in period with created→approved deltas of 5, 10, 20 days.""" for i, gap in enumerate((5, 10, 20)): created = date(2026, 2, 1) @@ -466,7 +466,7 @@ def test_median_p80_sample_size(self): # p80 over [5, 10, 20] (n=3, idx round(0.8*2)=2) = 20 self.assertAlmostEqual(v["p80_days"], 20.0) - def test_pre_window_creation_resolved_for_in_window_approval(self): + def test_pre_window_creation_resolved_for_in_window_approval(self) -> None: """A job created long before the reporting window but approved inside it must have its (pre-window) creation event fetched — the Tier 1 fetch-narrowing regressed this until the relevant-jobs @@ -518,7 +518,7 @@ def test_pre_window_creation_resolved_for_in_window_approval(self): } self.assertEqual(missing_anchor_codes, set()) - def test_missing_creation_anchor_excludes_and_warns(self): + def test_missing_creation_anchor_excludes_and_warns(self) -> None: """A velocity-relevant event in-period on a job missing its job_created anchor must produce a warning, not silent exclusion.""" job = self._make_job( @@ -556,7 +556,7 @@ def setUp(self) -> None: self.start = date(2026, 3, 1) self.end = date(2026, 3, 31) - def test_categorization_is_mutually_exclusive(self): + def test_categorization_is_mutually_exclusive(self) -> None: # Accepted via quote_accepted j_acc = self._make_job( name="Acc", company=self.client_obj, created_dt=_nz_dt(date(2026, 3, 2)) @@ -618,7 +618,7 @@ def test_categorization_is_mutually_exclusive(self): total = sum(f[k]["count"] for k in f) self.assertEqual(total, 5) - def test_missing_creation_anchor_excludes_and_warns(self): + def test_missing_creation_anchor_excludes_and_warns(self) -> None: """A job with events in the reporting period but no usable job_created anchor must produce a funnel warning rather than being silently dropped.""" @@ -652,7 +652,7 @@ def setUp(self) -> None: defaults.daily_approved_hours_target = Decimal("8.00") defaults.save() - def test_rolling_average_derived_from_weekly_series(self): + def test_rolling_average_derived_from_weekly_series(self) -> None: """Build approvals across three weeks; verify the rolling average for the last week equals the mean of the underlying weekly series.""" end_date = date(2026, 3, 22) # Sunday — last week ends here @@ -692,14 +692,14 @@ def test_rolling_average_derived_from_weekly_series(self): sum(approved_series) / 3.0, ) - def test_working_days_match_existing_logic(self): + def test_working_days_match_existing_logic(self) -> None: # 2026-03-02 (Mon) to 2026-03-06 (Fri) = 5 weekdays, no NZ holidays rep = SalesPipelineService.get_report(date(2026, 3, 2), date(2026, 3, 6), 4, 13) self.assertEqual(rep["scoreboard"]["working_days"], 5) class PeriodDefaultTests(SalesPipelineServiceFixturesMixin, BaseTestCase): - def test_no_explicit_dates_reasonable(self): + def test_no_explicit_dates_reasonable(self) -> None: # Even with no jobs, the report should produce a coherent shape rep = SalesPipelineService.get_report( date(2026, 1, 1), timezone.localdate(), 4, 13 diff --git a/apps/accounts/migrations/0004_forbid_blank_text.py b/apps/accounts/migrations/0004_forbid_blank_text.py new file mode 100644 index 000000000..85159b1fb --- /dev/null +++ b/apps/accounts/migrations/0004_forbid_blank_text.py @@ -0,0 +1,44 @@ +"""Forbid empty strings in nullable text columns (accounts). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "accounts_staff": [ + "preferred_name", + "xero_user_id" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("accounts", "0003_seed_system_automation_user"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/accounts/models.py b/apps/accounts/models.py index fbe85628f..0a96d2ad3 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -103,7 +103,6 @@ class Staff(AbstractBaseUser, PermissionsMixin): "date_joined", "created_at", "updated_at", - "last_login", "groups", "user_permissions", ] @@ -111,6 +110,10 @@ class Staff(AbstractBaseUser, PermissionsMixin): # Internal fields not exposed via API (write-only or internal use). # `icon` is deliberately absent: it is written only by the dedicated icon # upload endpoint and read via the icon_url property. + # `last_login` is deliberately absent: authentication is JWT-only + # (`SIMPLE_JWT` leaves `UPDATE_LAST_LOGIN` at its `False` default and + # `authenticate()` never emits `user_logged_in`), so the inherited + # `AbstractBaseUser` column is never written and has no meaning to expose. STAFF_INTERNAL_FIELDS = [ "password", ] diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py index b62fc113c..a2869732a 100644 --- a/apps/accounts/serializers.py +++ b/apps/accounts/serializers.py @@ -6,6 +6,7 @@ from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from apps.accounts.models import Staff +from apps.workflow.serializers_base import NullUnsetModelSerializer logger = logging.getLogger(__name__) @@ -69,7 +70,7 @@ def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]: raise serializers.ValidationError("Invalid credentials") -class StaffSerializer(serializers.ModelSerializer[Staff]): +class StaffSerializer(NullUnsetModelSerializer[Staff]): icon_url = serializers.SerializerMethodField(read_only=True) def get_icon_url(self, obj: Staff) -> Optional[str]: @@ -91,7 +92,6 @@ class Meta: read_only_fields = [ "id", "wage_rate", - "last_login", "date_joined", "created_at", "updated_at", @@ -106,7 +106,7 @@ class Meta: } -class StaffCreateSerializer(serializers.ModelSerializer[Staff]): +class StaffCreateSerializer(NullUnsetModelSerializer[Staff]): icon_url = serializers.SerializerMethodField(read_only=True) def get_icon_url(self, obj: Staff) -> Optional[str]: @@ -150,7 +150,7 @@ class Meta: } -class KanbanStaffSerializer(serializers.ModelSerializer): +class KanbanStaffSerializer(NullUnsetModelSerializer[Staff]): display_name = serializers.CharField( source="get_display_full_name", read_only=True, @@ -174,7 +174,7 @@ class Meta: read_only_fields = ["display_name", "is_office_staff", "is_workshop_staff"] -class UserProfileSerializer(serializers.ModelSerializer): +class UserProfileSerializer(NullUnsetModelSerializer[Staff]): """Serializer for user profile information returned by /accounts/me/""" username = serializers.CharField(source="email", read_only=True) diff --git a/apps/accounts/tests/test_auth_observability.py b/apps/accounts/tests/test_auth_observability.py index 98e9e9634..f08932e74 100644 --- a/apps/accounts/tests/test_auth_observability.py +++ b/apps/accounts/tests/test_auth_observability.py @@ -4,10 +4,12 @@ from django.test import RequestFactory, override_settings from rest_framework.test import APIClient +from apps.accounts.models import Staff from apps.accounts.views import user_profile_view from apps.accounts.views.user_profile_view import LogoutUserAPIView -from apps.workflow import authentication +from apps.workflow import authentication, exception_handlers from apps.workflow.authentication import JWTAuthentication +from apps.workflow.models import AppError TEST_CLIENT_IP = "192.0.2.10" TEST_WEBHOOK_IP = "192.0.2.20" @@ -15,7 +17,7 @@ @override_settings(ENABLE_JWT_AUTH=True) -def test_jwt_auth_logs_cookie_miss_with_request_context(): +def test_jwt_auth_logs_cookie_miss_with_request_context() -> None: request = RequestFactory().get( "/api/accounts/me/", HTTP_X_FORWARDED_FOR=f"{TEST_CLIENT_IP}, {TEST_PROXY_IP}", @@ -24,19 +26,20 @@ def test_jwt_auth_logs_cookie_miss_with_request_context(): with patch.object(authentication.logger, "info") as log_info: assert JWTAuthentication().authenticate(request) is None - log_info.assert_called_once_with( - "JWT AUTH MISS - method=%s path=%s ip=%s access_cookie=%s access_cookie_present=%s refresh_cookie_present=%s", + log_info.assert_called_once() + _, *log_args = log_info.call_args.args + assert log_args == [ "GET", "/api/accounts/me/", TEST_CLIENT_IP, "access_token", False, False, - ) + ] @override_settings(ENABLE_JWT_AUTH=True) -def test_jwt_auth_does_not_log_cookie_miss_for_xero_webhook(): +def test_jwt_auth_does_not_log_cookie_miss_for_xero_webhook() -> None: request = RequestFactory().post( "/api/xero/webhook/", HTTP_X_FORWARDED_FOR=f"{TEST_WEBHOOK_IP}, {TEST_PROXY_IP}", @@ -49,7 +52,55 @@ def test_jwt_auth_does_not_log_cookie_miss_for_xero_webhook(): @pytest.mark.django_db -def test_logout_logs_cookie_presence_without_token_values(): +@override_settings(ENABLE_JWT_AUTH=True) +def test_current_user_anonymous_session_probe_is_not_an_auth_warning() -> None: + client = APIClient() + + with patch.object(exception_handlers.auth_logger, "warning") as log_warning: + response = client.get("/api/accounts/me/") + + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + log_warning.assert_not_called() + assert AppError.objects.count() == 0 + + +@pytest.mark.django_db +@override_settings(ENABLE_JWT_AUTH=True) +def test_current_user_returns_authenticated_profile() -> None: + user = Staff.objects.create_user( + email="profile@example.test", + password="testpass", + first_name="Profile", + last_name="User", + ) + client = APIClient() + client.force_authenticate(user=user) + + response = client.get("/api/accounts/me/") + + assert response.status_code == 200 + assert response.json()["email"] == user.email + + +@pytest.mark.django_db +@override_settings(ENABLE_JWT_AUTH=True, DEBUG=False) +def test_current_user_invalid_cookie_remains_an_auth_warning() -> None: + client = APIClient() + client.cookies["access_token"] = "not-a-valid-jwt" + + with patch.object(exception_handlers.auth_logger, "warning") as log_warning: + response = client.get("/api/accounts/me/") + + assert response.status_code == 401 + log_warning.assert_called_once() + assert AppError.objects.count() == 1 + + +@pytest.mark.django_db +def test_logout_logs_cookie_presence_without_token_values() -> None: request = RequestFactory().post( "/api/accounts/logout/", HTTP_X_FORWARDED_FOR=f"{TEST_CLIENT_IP}, {TEST_PROXY_IP}", @@ -61,16 +112,13 @@ def test_logout_logs_cookie_presence_without_token_values(): response = LogoutUserAPIView.as_view()(request) assert response.status_code == 200 - log_info.assert_called_once_with( - "JWT LOGOUT REQUEST - ip=%s access_cookie_present=%s refresh_cookie_present=%s", - TEST_CLIENT_IP, - True, - True, - ) + log_info.assert_called_once() + _, *log_args = log_info.call_args.args + assert log_args == [TEST_CLIENT_IP, True, True] @pytest.mark.django_db -def test_logout_clears_cookies_even_when_access_cookie_is_invalid(): +def test_logout_clears_cookies_even_when_access_cookie_is_invalid() -> None: client = APIClient() client.cookies["access_token"] = "not-a-valid-jwt" client.cookies["refresh_token"] = "refresh-token-value" diff --git a/apps/accounts/tests/test_automation_user.py b/apps/accounts/tests/test_automation_user.py index a9413c85a..616c9b91c 100644 --- a/apps/accounts/tests/test_automation_user.py +++ b/apps/accounts/tests/test_automation_user.py @@ -10,7 +10,7 @@ class GetAutomationUserTest(BaseTestCase): seeded identity and the failure mode when the row is absent. """ - def test_returns_seeded_system_automation_user(self): + def test_returns_seeded_system_automation_user(self) -> None: """A seed-data refactor could accidentally make automation privileged. The assertions pin the identity to the system row and prove it cannot @@ -26,7 +26,7 @@ def test_returns_seeded_system_automation_user(self): self.assertFalse(user.is_workshop_staff) self.assertFalse(user.has_usable_password()) - def test_raises_runtime_error_when_row_missing(self): + def test_raises_runtime_error_when_row_missing(self) -> None: """Missing seed data must fail before work is attributed incorrectly. This catches a fallback to an arbitrary staff row or silent ``None`` diff --git a/apps/accounts/tests/test_displayable_staff.py b/apps/accounts/tests/test_displayable_staff.py index e3688104d..34be2daa1 100644 --- a/apps/accounts/tests/test_displayable_staff.py +++ b/apps/accounts/tests/test_displayable_staff.py @@ -43,7 +43,7 @@ class GetPayrollExcludedStaffIdsTest(BaseTestCase): eligible. """ - def test_excludes_staff_with_null_xero_id(self): + def test_excludes_staff_with_null_xero_id(self) -> None: """A blank Xero ID could leak admin users into payroll exports. The test creates that exact missing-ID shape and requires it in the @@ -52,16 +52,16 @@ def test_excludes_staff_with_null_xero_id(self): admin = _make_staff(email="no-xero@example.com", xero_user_id=None) self.assertIn(str(admin.id), get_payroll_excluded_staff_ids()) - def test_excludes_staff_with_empty_xero_id(self): + def test_excludes_staff_with_unset_xero_id(self) -> None: """Empty strings are another missing-ID shape from forms/imports. This catches code that only checks ``is None`` and would therefore send an unenrolled staff member to Xero payroll. """ - admin = _make_staff(email="empty-xero@example.com", xero_user_id="") + admin = _make_staff(email="empty-xero@example.com", xero_user_id=None) self.assertIn(str(admin.id), get_payroll_excluded_staff_ids()) - def test_excludes_staff_with_non_uuid_xero_id(self): + def test_excludes_staff_with_non_uuid_xero_id(self) -> None: """Malformed IDs must not be treated as payroll enrollment. This catches a broad truthiness check that would let placeholder text @@ -70,7 +70,7 @@ def test_excludes_staff_with_non_uuid_xero_id(self): admin = _make_staff(email="garbage-xero@example.com", xero_user_id="not-a-uuid") self.assertIn(str(admin.id), get_payroll_excluded_staff_ids()) - def test_includes_staff_with_valid_uuid_xero_id(self): + def test_includes_staff_with_valid_uuid_xero_id(self) -> None: """The invalid-ID guard must not exclude real payroll staff. This catches an over-broad exclusion rule by proving a valid Xero UUID @@ -82,7 +82,7 @@ def test_includes_staff_with_valid_uuid_xero_id(self): ) self.assertNotIn(str(employee.id), get_payroll_excluded_staff_ids()) - def test_does_not_depend_on_date_left(self): + def test_does_not_depend_on_date_left(self) -> None: """Payroll exclusion is about Xero identity, not current employment. This catches a refactor that derives the exclusion set only from active @@ -104,7 +104,7 @@ class GetDisplayableStaffTargetDateTest(BaseTestCase): path that must still render. """ - def test_excludes_no_xero_id_staff(self): + def test_excludes_no_xero_id_staff(self) -> None: """Workshop views must not display staff who cannot be paid in Xero. This catches dropping the payroll-enrollment filter while building the @@ -121,7 +121,7 @@ def test_excludes_no_xero_id_staff(self): get_displayable_staff(target_date=target), ) - def test_excludes_staff_who_have_left(self): + def test_excludes_staff_who_have_left(self) -> None: """Departed payroll staff must disappear after their leave date. This catches comparing only join dates, or forgetting the leave-date @@ -138,7 +138,7 @@ def test_excludes_staff_who_have_left(self): get_displayable_staff(target_date=target), ) - def test_includes_active_payroll_enrolled_staff(self): + def test_includes_active_payroll_enrolled_staff(self) -> None: """The exclusion rules must not hide a normal active employee. This catches over-tightening the target-date query by proving an @@ -163,7 +163,7 @@ class GetDisplayableStaffDateRangeTest(BaseTestCase): active on Monday) didn't include them, but the date-range queryset did. """ - def test_no_xero_staff_joining_midweek_is_excluded_from_weekly_range(self): + def test_no_xero_staff_joining_midweek_is_excluded_from_weekly_range(self) -> None: """Mid-week joins can bypass a Monday-only exclusion calculation. This catches that production bug by making the no-Xero staff inactive @@ -183,7 +183,7 @@ def test_no_xero_staff_joining_midweek_is_excluded_from_weekly_range(self): self.assertNotIn(late_admin, result) - def test_no_xero_staff_active_full_range_is_excluded(self): + def test_no_xero_staff_active_full_range_is_excluded(self) -> None: """No-Xero staff must be filtered even when active for the whole week. This catches a range query that only applies employment dates and @@ -199,7 +199,7 @@ def test_no_xero_staff_active_full_range_is_excluded(self): self.assertNotIn(admin, get_displayable_staff(date_range=(monday, sunday))) - def test_left_staff_is_excluded_from_range_after_their_departure(self): + def test_left_staff_is_excluded_from_range_after_their_departure(self) -> None: """A departed employee must not reappear in future weekly views. This catches using only the range start/join date without enforcing @@ -215,7 +215,7 @@ def test_left_staff_is_excluded_from_range_after_their_departure(self): self.assertNotIn(gone, get_displayable_staff(date_range=(monday, sunday))) - def test_payroll_enrolled_staff_active_in_range_is_included(self): + def test_payroll_enrolled_staff_active_in_range_is_included(self) -> None: """The weekly filters must keep legitimate payroll staff visible. This catches an overcorrection for admin leakage that would hide a diff --git a/apps/accounts/tests/test_staff_api.py b/apps/accounts/tests/test_staff_api.py index e143d9ebf..0fd6e1c33 100644 --- a/apps/accounts/tests/test_staff_api.py +++ b/apps/accounts/tests/test_staff_api.py @@ -24,7 +24,7 @@ def _png_bytes(size: int = 8) -> bytes: class StaffListCreateAPIViewTests(BaseTestCase): - def test_staff_list_prefetches_groups_for_serializer(self): + def test_staff_list_prefetches_groups_for_serializer(self) -> None: office_user = Staff.objects.create_user( email="office@example.test", password="testpass", @@ -137,6 +137,33 @@ def test_create_leaves_a_new_staff_member_active(self) -> None: self.assertIsNone(created.date_left) self.assertTrue(created.is_currently_active) + def test_create_ignores_a_client_supplied_last_login(self) -> None: + """`last_login` is not part of the write contract. + + Authentication is JWT-only and nothing ever stamps the inherited + `AbstractBaseUser` column, so an API client must not be able to forge a + login time. The create serializer once listed only `wage_rate` as + read-only, which let this value through. + """ + response = self.client_api.post( + "/api/accounts/staff/", + { + "email": "forger@example.test", + "first_name": "For", + "last_name": "Ger", + "password": "TestPassword123!", + "base_wage_rate": 32.5, + "date_left": None, + "last_login": "2026-07-01T09:00:00Z", + }, + format="json", + ) + + self.assertEqual(response.status_code, 201, response.content) + self.assertNotIn("last_login", response.json()) + created = Staff.objects.get(email="forger@example.test") + self.assertIsNone(created.last_login) + def test_setting_date_left_offboards_a_staff_member(self) -> None: target = Staff.objects.create_user( email="leaving@example.test", diff --git a/apps/accounts/views/user_profile_view.py b/apps/accounts/views/user_profile_view.py index 1b39a58b9..34697874c 100644 --- a/apps/accounts/views/user_profile_view.py +++ b/apps/accounts/views/user_profile_view.py @@ -7,7 +7,7 @@ from django.conf import settings from drf_spectacular.utils import OpenApiTypes, extend_schema from rest_framework import status -from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -25,7 +25,7 @@ class GetCurrentUserAPIView(APIView): Get current authenticated user information via JWT from httpOnly cookie """ - permission_classes = [IsAuthenticated] + permission_classes = [AllowAny] serializer_class = UserProfileSerializer @extend_schema( @@ -33,7 +33,7 @@ class GetCurrentUserAPIView(APIView): responses={200: UserProfileSerializer}, ) def get(self, request: Request) -> Response: - # Defensive guard: return 401 when unauthenticated instead of 500 + # This endpoint is the SPA's session probe, so no session is an expected 401. user = getattr(request, "user", None) logger.info(f"[/ME] -> received request: {request} and user: {user}") if user is None or not getattr(user, "is_authenticated", False): diff --git a/apps/company/__init__.py b/apps/company/__init__.py index 2b2841847..bdf686e35 100644 --- a/apps/company/__init__.py +++ b/apps/company/__init__.py @@ -57,9 +57,6 @@ CompanyUpdateResponseSerializer, CompanyUpdateSerializer, ContactMethodSerializer, - JobPersonBaseSerializer, - JobPersonResponseSerializer, - JobPersonUpdateSerializer, StandardErrorSerializer, SupplierPickupAddressSerializer, SupplierSearchAliasCreateSerializer, @@ -100,9 +97,6 @@ "CompanyUpdateSerializer", "ContactMethod", "ContactMethodSerializer", - "JobPersonBaseSerializer", - "JobPersonResponseSerializer", - "JobPersonUpdateSerializer", "Person", "PersonCompanyLinkSerializer", "PersonCompanySummarySerializer", diff --git a/apps/company/migrations/0009_normalise_blank_text_to_null.py b/apps/company/migrations/0009_normalise_blank_text_to_null.py new file mode 100644 index 000000000..68e89d706 --- /dev/null +++ b/apps/company/migrations/0009_normalise_blank_text_to_null.py @@ -0,0 +1,28 @@ +"""Collapse empty-string text values to NULL on nullable Company fields. + +`address` and `email` are both `null=True, blank=True`, so "unset" had two +representations and consumers had to test for each. NULL is the single +unset value; the serializers now reject "" so it cannot come back. + +Irreversible: reverse cannot distinguish rows that were "" from rows that +were already NULL, so it is a no-op rather than a wrong restore. +""" + +from django.db import migrations + +TABLE = "company_company" +COLUMNS = ["address", "email"] + + +class Migration(migrations.Migration): + dependencies = [ + ("company", "0008_apply_reviewed_duplicate_cleanup"), + ] + + operations = [ + migrations.RunSQL( + sql=f"UPDATE {TABLE} SET {col} = NULL WHERE {col} = ''", + reverse_sql=migrations.RunSQL.noop, + ) + for col in COLUMNS + ] diff --git a/apps/company/migrations/0010_forbid_blank_text.py b/apps/company/migrations/0010_forbid_blank_text.py new file mode 100644 index 000000000..7c4336299 --- /dev/null +++ b/apps/company/migrations/0010_forbid_blank_text.py @@ -0,0 +1,61 @@ +"""Forbid empty strings in nullable text columns (company). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "company_company": [ + "address", + "email", + "xero_contact_id", + "xero_merged_into_id", + "xero_tenant_id" + ], + "company_companypersonlink": [ + "notes", + "position" + ], + "company_person": [ + "email" + ], + "company_supplierpickupaddress": [ + "google_place_id", + "notes", + "postal_code", + "state", + "suburb" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("company", "0009_normalise_blank_text_to_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/company/migrations/0011_text_unset_is_null.py b/apps/company/migrations/0011_text_unset_is_null.py new file mode 100644 index 000000000..3206fd85b --- /dev/null +++ b/apps/company/migrations/0011_text_unset_is_null.py @@ -0,0 +1,32 @@ +"""Give company's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("company", "0010_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="contactmethod", + name="label", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.RunSQL( + sql='UPDATE company_contactmethod SET "label" = NULL WHERE "label" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/company/migrations/0012_text_unset_constraints.py b/apps/company/migrations/0012_text_unset_constraints.py new file mode 100644 index 000000000..13747461a --- /dev/null +++ b/apps/company/migrations/0012_text_unset_constraints.py @@ -0,0 +1,34 @@ +"""Add the not-blank CHECK constraints for company's newly nullable columns. + +Separate from 0011_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "company_contactmethod": [ + "label" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("company", "0011_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/company/models.py b/apps/company/models.py index 887551080..9fe165c82 100644 --- a/apps/company/models.py +++ b/apps/company/models.py @@ -453,7 +453,7 @@ class Source(models.TextChoices): method_type = models.CharField(max_length=20, choices=MethodType.choices) value = models.CharField(max_length=255) normalized_value = models.CharField(max_length=255, db_index=True) - label = models.CharField(max_length=255, blank=True) + label = models.CharField(max_length=255, blank=True, null=True) is_primary = models.BooleanField(default=False) source = models.CharField( max_length=20, diff --git a/apps/company/person_serializers.py b/apps/company/person_serializers.py index 5707e50ee..65f8e8447 100644 --- a/apps/company/person_serializers.py +++ b/apps/company/person_serializers.py @@ -13,6 +13,7 @@ PhoneOwnershipResult, PhonePersonMatch, ) +from apps.workflow.serializers_base import NullUnsetModelSerializer class PersonCompanyLinkSerializer( @@ -31,7 +32,7 @@ class PersonCompanySummarySerializer(serializers.Serializer[dict[str, str]]): company_name = serializers.CharField() -class PersonSummarySerializer(serializers.ModelSerializer[Person]): +class PersonSummarySerializer(NullUnsetModelSerializer[Person]): # Declared explicitly so the response contract says always-present. The # ModelSerializer default would infer required=False from the model default, # which renders as optional in the schema even though every row carries it. @@ -94,7 +95,7 @@ def get_company_links(self, person: Person) -> list[PersonCompanyLinkData]: return list(PersonDirectoryService.company_links(person)) -class PersonIdentityUpdateSerializer(serializers.ModelSerializer[Person]): +class PersonIdentityUpdateSerializer(NullUnsetModelSerializer[Person]): class Meta: model = Person fields = ["name", "email"] @@ -102,13 +103,12 @@ class Meta: "name": {"required": False}, "email": { "required": False, - "allow_blank": True, "allow_null": True, }, } -class CompanyPersonSerializer(serializers.ModelSerializer[CompanyPersonLink]): +class CompanyPersonSerializer(NullUnsetModelSerializer[CompanyPersonLink]): person_id = serializers.UUIDField(source="person.id", read_only=True) person_name = serializers.CharField(source="person.name", read_only=True) person_email = serializers.EmailField( @@ -131,7 +131,7 @@ class Meta: class CompanyPersonCreateSerializer(serializers.Serializer[None]): name = serializers.CharField(max_length=255) - email = serializers.EmailField(required=False, allow_blank=True, allow_null=True) + email = serializers.EmailField(required=False, allow_null=True) phone = serializers.CharField(required=False, allow_blank=True, allow_null=True) position = serializers.CharField( required=False, allow_blank=True, allow_null=True, max_length=255 @@ -149,11 +149,9 @@ def validate_phone(self, value: str | None) -> str | None: class CompanyLinkWriteSerializer(serializers.Serializer[None]): position = serializers.CharField( - required=False, allow_blank=True, allow_null=True, max_length=255, default=None - ) - notes = serializers.CharField( - required=False, allow_blank=True, allow_null=True, default=None + required=False, allow_null=True, max_length=255, default=None ) + notes = serializers.CharField(required=False, allow_null=True, default=None) is_primary = serializers.BooleanField(required=False, default=False) @@ -202,6 +200,6 @@ class PersonContactMethodWriteSerializer(serializers.Serializer[None]): def get_fields(self) -> dict[str, "serializers.Field[Any, Any, Any, Any]"]: fields = super().get_fields() fields["label"] = serializers.CharField( - required=False, allow_blank=True, default="" + required=False, allow_null=True, default=None ) return fields diff --git a/apps/company/serializers.py b/apps/company/serializers.py index 31cf1f281..444c89c49 100644 --- a/apps/company/serializers.py +++ b/apps/company/serializers.py @@ -13,6 +13,7 @@ SupplierPickupAddress, SupplierSearchAlias, ) +from apps.workflow.serializers_base import NullUnsetModelSerializer def _ordered_company_links(person: Person) -> list[CompanyPersonLink]: @@ -128,13 +129,13 @@ class CompanyPersonLinkUpdateAttrs(TypedDict, total=False): phone: str | None -class CompanyPersonLinkSerializer(serializers.ModelSerializer): +class CompanyPersonLinkSerializer(NullUnsetModelSerializer[CompanyPersonLink]): """Serializer for a person's relationship to a company.""" person = serializers.UUIDField(source="person_id", read_only=True) person_name = serializers.CharField(source="person.name") person_email = serializers.EmailField( - source="person.email", required=False, allow_blank=True, allow_null=True + source="person.email", required=False, allow_null=True ) # Backed by ContactMethod: reads come from the viewset's # primary_phone_annotation; writes upsert the contact's primary method. @@ -167,17 +168,6 @@ class Meta: "notes": {"help_text": "Additional notes about this person"}, } - def to_internal_value(self, data: Any) -> Any: - """Convert empty strings to None for nullable fields before validation.""" - # Fields that should be NULL instead of empty string - nullable_fields = ["person_email", "position", "notes"] - - for field in nullable_fields: - if field in data and data[field] == "": - data[field] = None - - return super().to_internal_value(data) - def create(self, validated_data: CompanyPersonLinkCreateAttrs) -> CompanyPersonLink: raw_phone = validated_data.get("phone") # not a model field person_value = validated_data["person"] @@ -237,7 +227,7 @@ def _apply_phone( ).get(pk=link.pk) -class ContactMethodSerializer(serializers.ModelSerializer): +class ContactMethodSerializer(NullUnsetModelSerializer[ContactMethod]): """Serializer for canonical company/person phone and email methods.""" owner_company = serializers.SerializerMethodField() @@ -334,7 +324,7 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: return attrs -class SupplierPickupAddressSerializer(serializers.ModelSerializer): +class SupplierPickupAddressSerializer(NullUnsetModelSerializer[SupplierPickupAddress]): """Serializer for SupplierPickupAddress model (delivery/pickup locations).""" formatted_address = serializers.CharField(read_only=True) @@ -371,7 +361,7 @@ def to_internal_value(self, data: Any) -> Any: return super().to_internal_value(data) -class CompanySerializer(serializers.ModelSerializer): +class CompanySerializer(NullUnsetModelSerializer[Company]): contacts = CompanyPersonLinkSerializer(many=True, read_only=True) class Meta: @@ -390,7 +380,7 @@ class Meta: ) -class CompanyNameOnlySerializer(serializers.ModelSerializer): +class CompanyNameOnlySerializer(NullUnsetModelSerializer[Company]): class Meta: model = Company fields = ["id", "name"] @@ -437,7 +427,7 @@ class CompanySearchResponseSerializer(serializers.Serializer): total_pages = serializers.IntegerField() -class SupplierSearchAliasSerializer(serializers.ModelSerializer): +class SupplierSearchAliasSerializer(NullUnsetModelSerializer[SupplierSearchAlias]): """Supplier search alias attached to a company/contact.""" class Meta: @@ -462,7 +452,7 @@ class CompanyCreateSerializer(serializers.Serializer): """Serializer for company creation request""" name = serializers.CharField(max_length=255) - email = serializers.EmailField(required=False, allow_blank=True, allow_null=True) + email = serializers.EmailField(required=False, allow_null=True) # Stored as the client's primary ContactMethod, not a Client column phone = serializers.CharField(required=False, allow_blank=True, allow_null=True) address = serializers.CharField(required=False, allow_blank=True, allow_null=True) @@ -523,7 +513,7 @@ class CompanyUpdateSerializer(serializers.Serializer): """Serializer for company update request""" name = serializers.CharField(max_length=255, required=False) - email = serializers.EmailField(required=False, allow_blank=True) + email = serializers.EmailField(required=False, allow_null=True) # Stored as the client's primary ContactMethod, not a Client column phone = serializers.CharField(required=False, allow_blank=True, allow_null=True) address = serializers.CharField(required=False, allow_blank=True) @@ -539,22 +529,6 @@ class CompanyUpdateResponseSerializer(serializers.Serializer): message = serializers.CharField() -class JobPersonBaseSerializer(serializers.Serializer): - """Fields shared by the job person response and update serializers.""" - - id = serializers.UUIDField() - name = serializers.CharField() - email = serializers.CharField(allow_blank=True, allow_null=True) - - -class JobPersonResponseSerializer(JobPersonBaseSerializer): - """Serializer for job person information response""" - - -class JobPersonUpdateSerializer(JobPersonBaseSerializer): - """Serializer for job person update request""" - - class CompanyJobHeaderSerializer(serializers.Serializer): """Serializer for job header in company jobs list.""" diff --git a/apps/company/services/company_rest_service.py b/apps/company/services/company_rest_service.py index fc6e040a4..e982d12b5 100644 --- a/apps/company/services/company_rest_service.py +++ b/apps/company/services/company_rest_service.py @@ -14,8 +14,6 @@ if TYPE_CHECKING: from django_stubs_ext import WithAnnotations - from apps.accounts.models import Staff - from django.core.exceptions import ValidationError as DjangoValidationError from django.db import transaction from django.db.models import Case, IntegerField, Q, When @@ -453,127 +451,6 @@ def get_company_contacts(company_id: UUID) -> List[Dict[str, Any]]: ) raise - @staticmethod - def get_job_person(job_id: UUID) -> Dict[str, Any]: - """ - Retrieves person information for a specific job. - - Args: - job_id: Job UUID - - Returns: - Dict with person information - - Raises: - ValueError: If job not found or no person associated - """ - # Import here to avoid circular imports - from apps.job.models import Job - - try: - job = Job.objects.select_related("person").get(id=job_id) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc - except Exception as exc: - persist_app_error( - exc, - additional_context={ - "operation": "get_job_person", - "job_id": str(job_id), - }, - ) - raise - - if not job.person: - # Documented business validation failure should not be persisted - raise ValueError(f"No person associated with job {job_id}") - - person = job.person - try: - return { - "id": str(person.id), - "name": person.name, - "email": person.email, - } - except Exception as exc: - persist_app_error( - exc, - additional_context={ - "operation": "serialize_job_person", - "job_id": str(job_id), - }, - ) - raise - - @staticmethod - def update_job_person( - job_id: UUID, person_data: Dict[str, Any], user: "Staff" - ) -> Dict[str, Any]: - """ - Updates the person for a specific job. - - Args: - job_id: Job UUID - person_data: Person data to update - user: Staff performing the update - - Returns: - Dict with updated contact information - - Raises: - ValueError: If job not found, person not found, or validation fails - """ - try: - # Import here to avoid circular imports - from apps.company.models import Person - from apps.job.models import Job - - try: - job = Job.objects.select_related("company", "person").get(id=job_id) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc - - person_id = person_data.get("id") - if not person_id: - raise ValueError("Person ID is required") - - try: - person = Person.objects.get(id=person_id) - except Person.DoesNotExist as exc: - raise ValueError(f"Person with id {person_id} not found") from exc - - job.person = person - job.save(staff=user) - - logger.info( - f"Person {person_id} assigned to job {job_id}", - extra={ - "job_id": str(job_id), - "person_id": str(person_id), - "company_id": str(job.company_id), - "operation": "update_job_person", - }, - ) - - return { - "id": str(person.id), - "name": person.name, - "email": person.email, - } - - except ValueError: - raise - except Exception as exc: - persist_app_error( - exc, - additional_context={ - "operation": "update_job_person", - "job_id": str(job_id), - "person_id": person_data.get("id"), - }, - ) - raise - @staticmethod def _execute_company_search(query: str, limit: int): """ @@ -938,8 +815,8 @@ def _create_company_in_xero(company_data: Dict[str, Any]) -> Company: with transaction.atomic(): company = Company.objects.create( name=name, - email=company_data.get("email") or "", - address=company_data.get("address") or "", + email=company_data.get("email") or None, + address=company_data.get("address") or None, is_account_customer=company_data.get("is_account_customer", True), xero_last_modified=timezone.now(), ) diff --git a/apps/company/services/duplicate_person_report.py b/apps/company/services/duplicate_person_report.py index 7653de51c..91e09c252 100644 --- a/apps/company/services/duplicate_person_report.py +++ b/apps/company/services/duplicate_person_report.py @@ -31,7 +31,7 @@ class DuplicatePersonContactMethod(TypedDict): method_type: str value: str normalized_value: str - contact_label: str + contact_label: str | None is_primary: bool diff --git a/apps/company/tests/test_company_invoice_summary.py b/apps/company/tests/test_company_invoice_summary.py index 3ca54c1d9..51e63f239 100644 --- a/apps/company/tests/test_company_invoice_summary.py +++ b/apps/company/tests/test_company_invoice_summary.py @@ -139,7 +139,7 @@ def test_formatting_annotated_companies_does_not_query_invoice_metrics(db): class CompanyCreateInvoiceSummaryTests(BaseTestCase): - def test_create_company_response_formats_annotated_invoice_summary(self): + def test_create_company_response_formats_annotated_invoice_summary(self) -> None: """Create responses must match the company-search summary contract. This catches a view refactor that returns a raw Company payload without @@ -178,7 +178,7 @@ def test_create_company_response_formats_annotated_invoice_summary(self): assert response.data["company"]["last_invoice_date"] is None assert response.data["company"]["total_spend"] == "$0.00" - def test_create_company_cleans_up_local_row_when_xero_create_fails(self): + def test_create_company_cleans_up_local_row_when_xero_create_fails(self) -> None: """Failed Xero contact creation must not leave a half-created company.""" provider = MagicMock() provider.provider_name = "Xero" diff --git a/apps/company/tests/test_contact_methods.py b/apps/company/tests/test_contact_methods.py index 714ee9834..40dd59c44 100644 --- a/apps/company/tests/test_contact_methods.py +++ b/apps/company/tests/test_contact_methods.py @@ -1,5 +1,4 @@ import uuid -from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch from django.conf import settings @@ -11,9 +10,6 @@ from nplusone.core.profiler import Profiler from nplusone.ext.django.patch import apply_patches -if TYPE_CHECKING: - from apps.job.models import Job - from apps.company.models import Company, CompanyPersonLink, ContactMethod, Person from apps.company.services.company_rest_service import CompanyRestService from apps.crm.models import PhoneCallRecord @@ -329,50 +325,6 @@ def test_contact_annotation_returns_contact_primary_phone(self) -> None: self.assertEqual(annotated.phone, "021 222 222") -class UpdateJobContactTests(BaseTestCase): - """Guards that reassigning a job's contact persists to the job record.""" - - def _job_with_contact(self) -> "tuple[Job, Company, CompanyPersonLink]": - from apps.job.models import Job - from apps.workflow.models import XeroPayItem - - company = Company.objects.create( - name="Acme Ltd", xero_last_modified=timezone.now() - ) - contact = _link(company, "Jane Smith") - job: Job = Job.objects.create( - name="Contact Assignment Job", - company=company, - person=contact.person, - created_by=self.test_staff, - default_xero_pay_item=XeroPayItem.get_ordinary_time(), - staff=self.test_staff, - ) - return job, company, contact - - def test_update_job_person_persists_new_person(self) -> None: - job, company, _ = self._job_with_contact() - new_contact = _link(company, "Bob Brown") - - CompanyRestService.update_job_person( - job.id, {"id": str(new_contact.person_id)}, self.test_staff - ) - - job.refresh_from_db() - self.assertEqual(job.person_id, new_contact.person_id) - - def test_update_job_person_missing_person_is_business_error(self) -> None: - job, _company, _ = self._job_with_contact() - app_error_count = AppError.objects.count() - - with self.assertRaises(ValueError): - CompanyRestService.update_job_person( - job.id, {"id": str(uuid.uuid4())}, self.test_staff - ) - - self.assertEqual(AppError.objects.count(), app_error_count) - - class CompanyListPhoneTests(TestCase): """Guards the Phone column of the companies list (restored after the ContactMethod migration dropped it).""" @@ -1004,7 +956,11 @@ def _provider(self) -> MagicMock: return provider def _create(self, provider: MagicMock, **payload: str) -> Company: - data: dict[str, str] = {"name": "New Company", "email": "", "address": ""} + data: dict[str, str | None] = { + "name": "New Company", + "email": None, + "address": None, + } data.update(payload) with patch( "apps.company.services.company_rest_service.get_provider", diff --git a/apps/company/tests/test_get_company_for_xero.py b/apps/company/tests/test_get_company_for_xero.py index 214e36876..6665db2e5 100644 --- a/apps/company/tests/test_get_company_for_xero.py +++ b/apps/company/tests/test_get_company_for_xero.py @@ -7,6 +7,9 @@ attribute_map translates to PascalCase correctly. """ +from datetime import datetime +from typing import TypedDict, Unpack + from django.test import TestCase from django.utils import timezone from xero_python.accounting.models import Address, Contact, Phone @@ -15,11 +18,21 @@ from apps.company.models import Company, ContactMethod +class _CompanyOverrides(TypedDict, total=False): + name: str + email: str | None + address: str | None + xero_last_modified: datetime + xero_contact_id: str | None + is_account_customer: bool + phone: str | None + + class GetCompanyForXeroTests(TestCase): """Pin the wire-format contract independent of which push function consumes it.""" - def _make_company(self, **overrides): - defaults = { + def _make_company(self, **overrides: Unpack[_CompanyOverrides]) -> Company: + defaults: _CompanyOverrides = { "name": "Acme Ltd", "xero_last_modified": timezone.now(), } @@ -35,7 +48,7 @@ def _make_company(self, **overrides): ) return company - def test_returns_sdk_contact_instance(self): + def test_returns_sdk_contact_instance(self) -> None: """The payload must be a xero_python Contact, not a dict. If anyone reverts to a snake_case dict, this fails. The SDK's @@ -48,21 +61,21 @@ def test_returns_sdk_contact_instance(self): self.assertIsInstance(payload, Contact) - def test_phones_are_sdk_phone_instances(self): + def test_phones_are_sdk_phone_instances(self) -> None: company = self._make_company(phone="027 351 8326") payload = company.get_company_for_xero() self.assertIsInstance(payload.phones[0], Phone) - def test_addresses_are_sdk_address_instances(self): + def test_addresses_are_sdk_address_instances(self) -> None: company = self._make_company(address="123 Test Street") payload = company.get_company_for_xero() self.assertIsInstance(payload.addresses[0], Address) - def test_populated_company_carries_all_fields(self): + def test_populated_company_carries_all_fields(self) -> None: """Every field a fully-populated Company supplies must reach the Contact.""" company = self._make_company( email="info@acme.test", @@ -82,7 +95,7 @@ def test_populated_company_carries_all_fields(self): self.assertEqual(payload.addresses[0].address_line1, "123 Test Street") self.assertEqual(payload.addresses[0].attention_to, "Acme Ltd") - def test_existing_company_populates_contact_id_on_instance(self): + def test_existing_company_populates_contact_id_on_instance(self) -> None: """contact_id lives on the Contact, not as a caller-side dict mutation. Regression for the dict-mutation pattern at push.py:40 @@ -95,7 +108,7 @@ def test_existing_company_populates_contact_id_on_instance(self): self.assertEqual(payload.contact_id, xero_id) - def test_no_email_emits_none_not_empty_string(self): + def test_no_email_emits_none_not_empty_string(self) -> None: """An unset email must not push an empty string to Xero. Empty string overwrites any operator-typed email Xero already holds @@ -107,21 +120,21 @@ def test_no_email_emits_none_not_empty_string(self): self.assertIsNone(payload.email_address) - def test_no_phone_emits_none_not_empty_string(self): + def test_no_phone_emits_none_not_empty_string(self) -> None: company = self._make_company(phone=None) payload = company.get_company_for_xero() self.assertIsNone(payload.phones[0].phone_number) - def test_no_address_emits_none_not_empty_string(self): + def test_no_address_emits_none_not_empty_string(self) -> None: company = self._make_company(address=None) payload = company.get_company_for_xero() self.assertIsNone(payload.addresses[0].address_line1) - def test_missing_name_raises_value_error(self): + def test_missing_name_raises_value_error(self) -> None: """The name guard must still trip.""" company = self._make_company() company.name = "" @@ -129,7 +142,7 @@ def test_missing_name_raises_value_error(self): with self.assertRaises(ValueError): company.get_company_for_xero() - def test_serialized_wire_format_is_pascalcase(self): + def test_serialized_wire_format_is_pascalcase(self) -> None: """End-to-end wire format check — the bytes Xero would actually receive. The original bug shipped snake_case JSON (`name`, `email_address`, …) diff --git a/apps/company/tests/test_job_contact_view.py b/apps/company/tests/test_job_contact_view.py deleted file mode 100644 index e4eb101e9..000000000 --- a/apps/company/tests/test_job_contact_view.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Job person endpoint tests. - -Guards the GET/PUT contract of the job-person endpoint end-to-end (URL -routing, permissions, and response-serializer validation), which the -job-settings tab uses to load and reassign a job's person. -""" - -from typing import TYPE_CHECKING, Any - -from django.urls import reverse -from django.utils import timezone - -if TYPE_CHECKING: - from rest_framework.response import _MonkeyPatchedResponse - -from apps.company.models import Company, CompanyPersonLink, Person -from apps.job.models import Job -from apps.testing import BaseAPITestCase -from apps.workflow.models import XeroPayItem - - -class JobPersonViewTests(BaseAPITestCase): - def setUp(self) -> None: - super().setUp() - self.client.force_authenticate(user=self.test_staff) - self.job_company = Company.objects.create( - name="Acme Ltd", xero_last_modified=timezone.now() - ) - - def _link(self, name: str) -> CompanyPersonLink: - person = Person.objects.create(name=name) - return CompanyPersonLink.objects.create( - company=self.job_company, - person=person, - ) - - def _job(self, link: CompanyPersonLink) -> Job: - job: Job = Job.objects.create( - name="Person Job", - company=self.job_company, - person=link.person, - created_by=self.test_staff, - default_xero_pay_item=XeroPayItem.get_ordinary_time(), - staff=self.test_staff, - ) - return job - - def _url(self, job: Job) -> str: - return reverse("companies:job_person_rest", kwargs={"job_id": job.id}) - - def _get(self, job: Job) -> "_MonkeyPatchedResponse": - return self.client.get(self._url(job)) - - def _put(self, job: Job, payload: dict[str, Any]) -> "_MonkeyPatchedResponse": - return self.client.put(self._url(job), payload, format="json") - - def _put_payload(self, link: CompanyPersonLink) -> dict[str, Any]: - return { - "id": str(link.person_id), - "name": link.person.name, - "email": link.person.email, - } - - def test_get_returns_job_person(self) -> None: - link = self._link("Jane Smith") - job = self._job(link) - - response = self._get(job) - - self.assertEqual(response.status_code, 200) - body = response.json() - self.assertEqual(body["id"], str(link.person_id)) - self.assertEqual(body["name"], "Jane Smith") - - def test_put_response_reflects_new_person(self) -> None: - job = self._job(self._link("Jane Smith")) - replacement = self._link("Bob Jones") - - response = self._put(job, self._put_payload(replacement)) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["id"], str(replacement.person_id)) - job.refresh_from_db() - self.assertEqual(job.person_id, replacement.person_id) - - def test_put_ignores_unrecognized_field(self) -> None: - """Phones live in ContactMethod; the job person update path - only reassigns the person FK. An unexpected key (here the removed - phone field) must be ignored, not written or echoed back.""" - job = self._job(self._link("Jane Smith")) - replacement = self._link("Bob Jones") - payload = self._put_payload(replacement) | {"phone": "09 999 9999"} - - response = self._put(job, payload) - - self.assertEqual(response.status_code, 200) - body = response.json() - self.assertEqual(body["id"], str(replacement.person_id)) - self.assertNotIn("phone", body) - job.refresh_from_db() - self.assertEqual(job.person_id, replacement.person_id) diff --git a/apps/company/tests/test_person_api.py b/apps/company/tests/test_person_api.py index f8c24300b..bbf398b73 100644 --- a/apps/company/tests/test_person_api.py +++ b/apps/company/tests/test_person_api.py @@ -311,7 +311,7 @@ def test_restore_link_over_http_unarchives_archived_person(self) -> None: with patch("apps.crm.tasks.rematch_phone_calls_task.delay"): response = self.client.put( f"/api/people/{person.id}/company-links/{self.company_a.id}/", - data={"position": "", "notes": "", "is_primary": False}, + data={"position": None, "notes": None, "is_primary": False}, format="json", ) self.assertEqual(response.status_code, 200) diff --git a/apps/company/urls_rest.py b/apps/company/urls_rest.py index 73be36ea4..eab4e7267 100644 --- a/apps/company/urls_rest.py +++ b/apps/company/urls_rest.py @@ -18,7 +18,6 @@ CompanyRetrieveRestView, CompanySearchRestView, CompanyUpdateRestView, - JobPersonRestView, ) from apps.company.views.contact_method_viewset import ContactMethodViewSet from apps.company.views.person_views import ( @@ -103,12 +102,6 @@ SupplierAliasDetailView.as_view(), name="supplier_alias_detail_rest", ), - # Job person REST endpoint - path( - "jobs//person/", - JobPersonRestView.as_view(), - name="job_person_rest", - ), # Address validation endpoint path( "addresses/validate/", diff --git a/apps/company/views/__init__.py b/apps/company/views/__init__.py index 8f75491e6..f356a43a4 100644 --- a/apps/company/views/__init__.py +++ b/apps/company/views/__init__.py @@ -8,7 +8,6 @@ CompanyRetrieveRestView, CompanySearchRestView, CompanyUpdateRestView, - JobPersonRestView, ) from .person_views import ( CompanyPeopleView, @@ -49,7 +48,6 @@ "CompanySupplierAliasListCreateView", "CompanyUpdateRestView", "ContactMethodViewSet", - "JobPersonRestView", "PersonArchiveView", "PersonCompanyLinkDetailView", "PersonCompanyLinksView", diff --git a/apps/company/views/company_rest_views.py b/apps/company/views/company_rest_views.py index 30c291807..8fbed3700 100644 --- a/apps/company/views/company_rest_views.py +++ b/apps/company/views/company_rest_views.py @@ -10,7 +10,6 @@ import logging from typing import Any, Dict -from uuid import UUID from drf_spectacular.utils import ( OpenApiParameter, @@ -24,7 +23,6 @@ from rest_framework.response import Response from rest_framework.views import APIView -from apps.accounts.models import Staff from apps.company.models import Company, ContactMethod from apps.company.serializers import ( CompanyCreateResponseSerializer, @@ -38,8 +36,6 @@ CompanySearchResponseSerializer, CompanyUpdateResponseSerializer, CompanyUpdateSerializer, - JobPersonResponseSerializer, - JobPersonUpdateSerializer, ) from apps.company.services.company_rest_service import CompanyRestService from apps.workflow.services.error_persistence import persist_app_error @@ -497,145 +493,6 @@ def post(self, request: Request) -> Response: ) -@extend_schema_view( - get=extend_schema( - summary="Get job person", - description="Retrieve person information for a specific job.", - parameters=[ - OpenApiParameter( - name="job_id", - location=OpenApiParameter.PATH, - description="UUID of the job", - required=True, - type=OpenApiTypes.UUID, - ) - ], - responses={ - 200: JobPersonResponseSerializer, - 404: CompanyErrorResponseSerializer, - 500: CompanyErrorResponseSerializer, - }, - tags=["Companies"], - ), - put=extend_schema( - summary="Update job person", - description="Update the person associated with a specific job.", - parameters=[ - OpenApiParameter( - name="job_id", - location=OpenApiParameter.PATH, - description="UUID of the job", - required=True, - type=OpenApiTypes.UUID, - ) - ], - request=JobPersonUpdateSerializer, - responses={ - 200: JobPersonResponseSerializer, - 400: CompanyErrorResponseSerializer, - 404: CompanyErrorResponseSerializer, - 500: CompanyErrorResponseSerializer, - }, - tags=["Companies"], - operation_id="companies_jobs_person_update", - ), -) -class JobPersonRestView(APIView): - """ - REST view for person information operations for a job. - Handles both retrieving and updating the person associated with a specific job. - """ - - permission_classes = [IsAuthenticated] - serializer_class = JobPersonResponseSerializer - - def get_serializer_class(self): - """Return the appropriate serializer class based on the request method""" - if self.request.method == "PUT": - return JobPersonUpdateSerializer - return JobPersonResponseSerializer - - def get(self, request: Request, job_id: UUID) -> Response: - """ - Retrieves person information for a specific job. - """ - try: - # Guard clause: validate job_id - if not job_id: - error_serializer = CompanyErrorResponseSerializer( - data={"error": "Job ID is required"} - ) - error_serializer.is_valid(raise_exception=True) - return Response( - error_serializer.data, status=status.HTTP_400_BAD_REQUEST - ) - - person_data = CompanyRestService.get_job_person(job_id) - serializer = JobPersonResponseSerializer(data=person_data) - serializer.is_valid(raise_exception=True) - return Response(serializer.data) - - except ValueError as e: - error_serializer = CompanyErrorResponseSerializer(data={"error": str(e)}) - error_serializer.is_valid(raise_exception=True) - return Response(error_serializer.data, status=status.HTTP_404_NOT_FOUND) - except Exception as exc: - return _build_server_error_response( - message="Error retrieving job person", exc=exc - ) - - def put(self, request: Request, job_id: UUID) -> Response: - """ - Updates the person for a specific job. - """ - try: - # Guard clause: validate job_id - if not job_id: - error_serializer = CompanyErrorResponseSerializer( - data={"error": "Job ID is required"} - ) - error_serializer.is_valid(raise_exception=True) - return Response( - error_serializer.data, status=status.HTTP_400_BAD_REQUEST - ) - - # Validate input data - input_serializer = JobPersonUpdateSerializer(data=request.data) - if not input_serializer.is_valid(): - error_serializer = CompanyErrorResponseSerializer( - data={"error": f"Invalid input data: {input_serializer.errors}"} - ) - error_serializer.is_valid(raise_exception=True) - return Response( - error_serializer.data, status=status.HTTP_400_BAD_REQUEST - ) - - person_data = input_serializer.validated_data - if not isinstance(request.user, Staff): - error_serializer = CompanyErrorResponseSerializer( - data={"error": "Authentication required"} - ) - error_serializer.is_valid(raise_exception=True) - return Response( - error_serializer.data, status=status.HTTP_401_UNAUTHORIZED - ) - updated_person = CompanyRestService.update_job_person( - job_id, person_data, request.user - ) - serializer = JobPersonResponseSerializer(data=updated_person) - serializer.is_valid(raise_exception=True) - return Response(serializer.data) - - except ValueError as e: - error_serializer = CompanyErrorResponseSerializer(data={"error": str(e)}) - error_serializer.is_valid(raise_exception=True) - return Response(error_serializer.data, status=status.HTTP_404_NOT_FOUND) - except Exception as exc: - return _build_server_error_response( - message="Error updating job person", exc=exc - ) - - @extend_schema_view( get=extend_schema( summary="Get company jobs", diff --git a/apps/crm/migrations/0007_forbid_blank_text.py b/apps/crm/migrations/0007_forbid_blank_text.py new file mode 100644 index 000000000..c21b38ac0 --- /dev/null +++ b/apps/crm/migrations/0007_forbid_blank_text.py @@ -0,0 +1,45 @@ +"""Forbid empty strings in nullable text columns (crm). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "crm_phoneprovidersettings": [ + "base_url", + "password", + "username" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("crm", "0006_remove_phonecallrecord_contact_and_more"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/crm/migrations/0008_text_unset_is_null.py b/apps/crm/migrations/0008_text_unset_is_null.py new file mode 100644 index 000000000..d000de4c4 --- /dev/null +++ b/apps/crm/migrations/0008_text_unset_is_null.py @@ -0,0 +1,176 @@ +"""Give crm's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("crm", "0007_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="phonecallrecord", + name="call_type", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="description", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="destination", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="external_number", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="normalized_destination", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="normalized_origin", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="origin", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="our_number", + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name="phonecallrecord", + name="status", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="archive_error", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="content_type", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="filename", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="provider_delete_error", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="sha256", + field=models.CharField(blank=True, max_length=64, null=True), + ), + migrations.AlterField( + model_name="phonecallrecording", + name="storage_path", + field=models.CharField(blank=True, max_length=500, null=True), + ), + migrations.AlterField( + model_name="phoneendpoint", + name="provider_account_code", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="phoneprovidersettings", + name="account_code", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.RunSQL( + sql='UPDATE crm_phoneendpoint SET "provider_account_code" = NULL WHERE "provider_account_code" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phoneprovidersettings SET "account_code" = NULL WHERE "account_code" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "call_type" = NULL WHERE "call_type" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "status" = NULL WHERE "status" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "description" = NULL WHERE "description" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "origin" = NULL WHERE "origin" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "destination" = NULL WHERE "destination" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "normalized_origin" = NULL WHERE "normalized_origin" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "normalized_destination" = NULL WHERE "normalized_destination" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "our_number" = NULL WHERE "our_number" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecord SET "external_number" = NULL WHERE "external_number" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "filename" = NULL WHERE "filename" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "storage_path" = NULL WHERE "storage_path" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "content_type" = NULL WHERE "content_type" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "sha256" = NULL WHERE "sha256" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "archive_error" = NULL WHERE "archive_error" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE crm_phonecallrecording SET "provider_delete_error" = NULL WHERE "provider_delete_error" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/crm/migrations/0009_text_unset_constraints.py b/apps/crm/migrations/0009_text_unset_constraints.py new file mode 100644 index 000000000..6c4dc0304 --- /dev/null +++ b/apps/crm/migrations/0009_text_unset_constraints.py @@ -0,0 +1,56 @@ +"""Add the not-blank CHECK constraints for crm's newly nullable columns. + +Separate from 0008_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "crm_phonecallrecord": [ + "call_type", + "description", + "destination", + "external_number", + "normalized_destination", + "normalized_origin", + "origin", + "our_number", + "status" + ], + "crm_phonecallrecording": [ + "archive_error", + "content_type", + "filename", + "provider_delete_error", + "sha256", + "storage_path" + ], + "crm_phoneendpoint": [ + "provider_account_code" + ], + "crm_phoneprovidersettings": [ + "account_code" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("crm", "0008_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/crm/models.py b/apps/crm/models.py index b507fafb0..c565d486d 100644 --- a/apps/crm/models.py +++ b/apps/crm/models.py @@ -31,7 +31,7 @@ class EndpointType(models.TextChoices): blank=True, related_name="phone_endpoints", ) - provider_account_code = models.CharField(max_length=100, blank=True) + provider_account_code = models.CharField(max_length=100, blank=True, null=True) provider_metadata = models.JSONField(default=dict, blank=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) @@ -116,7 +116,7 @@ class PhoneProviderSettings(models.Model): base_url = models.URLField(null=True, blank=True, default=None) username = EncryptedCharField(max_length=255, blank=True, null=True) password = EncryptedCharField(max_length=255, blank=True, null=True) - account_code = models.CharField(max_length=100, blank=True, default="") + account_code = models.CharField(max_length=100, blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -148,21 +148,21 @@ class Direction(models.TextChoices): call_datetime = models.DateTimeField(db_index=True) call_date = models.DateField(db_index=True) call_time = models.TimeField() - call_type = models.CharField(max_length=100, blank=True) - status = models.CharField(max_length=100, blank=True) - description = models.TextField(blank=True) - origin = models.CharField(max_length=150, blank=True) - destination = models.CharField(max_length=150, blank=True) - normalized_origin = models.CharField(max_length=150, blank=True) - normalized_destination = models.CharField(max_length=150, blank=True) + call_type = models.CharField(max_length=100, blank=True, null=True) + status = models.CharField(max_length=100, blank=True, null=True) + description = models.TextField(blank=True, null=True) + origin = models.CharField(max_length=150, blank=True, null=True) + destination = models.CharField(max_length=150, blank=True, null=True) + normalized_origin = models.CharField(max_length=150, blank=True, null=True) + normalized_destination = models.CharField(max_length=150, blank=True, null=True) direction = models.CharField( max_length=20, choices=Direction.choices, default=Direction.UNKNOWN, db_index=True, ) - our_number = models.CharField(max_length=150, blank=True) - external_number = models.CharField(max_length=150, blank=True) + our_number = models.CharField(max_length=150, blank=True, null=True) + external_number = models.CharField(max_length=150, blank=True, null=True) origin_endpoint = models.ForeignKey( PhoneEndpoint, on_delete=models.SET_NULL, @@ -263,8 +263,13 @@ def save( ) -> None: from apps.company.models import ContactMethod - self.normalized_origin = ContactMethod.normalize_phone(self.origin) - self.normalized_destination = ContactMethod.normalize_phone(self.destination) + # normalize_phone returns "" for "no number" because it also feeds + # ContactMethod.normalized_value, which is NOT NULL. These columns are + # nullable, so unset is NULL here. + self.normalized_origin = ContactMethod.normalize_phone(self.origin) or None + self.normalized_destination = ( + ContactMethod.normalize_phone(self.destination) or None + ) if update_fields is not None: fields = set(update_fields) if "origin" in fields: @@ -297,15 +302,15 @@ class PhoneCallRecording(models.Model): ) provider_recording_id = models.CharField(max_length=255, unique=True) account_code = models.CharField(max_length=100) - filename = models.CharField(max_length=255, blank=True) - storage_path = models.CharField(max_length=500, blank=True) - content_type = models.CharField(max_length=100, blank=True) + filename = models.CharField(max_length=255, blank=True, null=True) + storage_path = models.CharField(max_length=500, blank=True, null=True) + content_type = models.CharField(max_length=100, blank=True, null=True) byte_size = models.PositiveIntegerField(null=True, blank=True) - sha256 = models.CharField(max_length=64, blank=True) + sha256 = models.CharField(max_length=64, blank=True, null=True) archived_at = models.DateTimeField(null=True, blank=True, db_index=True) - archive_error = models.TextField(blank=True) + archive_error = models.TextField(blank=True, null=True) provider_deleted_at = models.DateTimeField(null=True, blank=True, db_index=True) - provider_delete_error = models.TextField(blank=True) + provider_delete_error = models.TextField(blank=True, null=True) local_deleted_at = models.DateTimeField(null=True, blank=True, db_index=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/apps/crm/serializers.py b/apps/crm/serializers.py index 5e9b3056f..0ea724059 100644 --- a/apps/crm/serializers.py +++ b/apps/crm/serializers.py @@ -13,6 +13,7 @@ from apps.crm.services.phone_call_service import ( normalize_phone, ) +from apps.workflow.serializers_base import NullUnsetModelSerializer if TYPE_CHECKING: from apps.accounts.models import Staff @@ -55,7 +56,7 @@ def _recording_download_url(recording: PhoneCallRecording) -> str | None: return f"/api/crm/phone-call-recordings/{recording.id}/download/" -class PhoneCallRecordingSerializer(serializers.ModelSerializer[PhoneCallRecording]): +class PhoneCallRecordingSerializer(NullUnsetModelSerializer[PhoneCallRecording]): download_url = serializers.SerializerMethodField() class Meta: @@ -83,7 +84,7 @@ def get_download_url(self, obj: PhoneCallRecording) -> str | None: return _recording_download_url(obj) -class PhoneEndpointSerializer(serializers.ModelSerializer[PhoneEndpoint]): +class PhoneEndpointSerializer(NullUnsetModelSerializer[PhoneEndpoint]): staff_name = serializers.SerializerMethodField() class Meta: @@ -187,21 +188,12 @@ class PhoneProviderSettingsAttrs(TypedDict, total=False): account_code: str -class PhoneProviderSettingsSerializer( - serializers.ModelSerializer[PhoneProviderSettings] -): +class PhoneProviderSettingsSerializer(NullUnsetModelSerializer[PhoneProviderSettings]): has_username = serializers.SerializerMethodField() has_password = serializers.SerializerMethodField() - username = serializers.CharField( - write_only=True, - required=False, - allow_blank=True, - ) - password = serializers.CharField( - write_only=True, - required=False, - allow_blank=True, - ) + # Omit either to leave the stored credential unchanged. + username = serializers.CharField(write_only=True, required=False) + password = serializers.CharField(write_only=True, required=False) class Meta: model = PhoneProviderSettings @@ -239,7 +231,7 @@ def get_has_password(self, obj: PhoneProviderSettings) -> bool: return bool(obj.password) -class PhoneCallRecordSerializer(serializers.ModelSerializer[PhoneCallRecord]): +class PhoneCallRecordSerializer(NullUnsetModelSerializer[PhoneCallRecord]): recording = serializers.SerializerMethodField() company_name = serializers.SerializerMethodField() diff --git a/apps/crm/services/phone_call_service.py b/apps/crm/services/phone_call_service.py index 3f8f5895c..066ec65f6 100644 --- a/apps/crm/services/phone_call_service.py +++ b/apps/crm/services/phone_call_service.py @@ -179,7 +179,7 @@ def delete_archived_provider_recordings(*, limit: int = 100) -> PhoneCallDeleteR else: deleted += 1 recording.provider_deleted_at = timezone.now() - recording.provider_delete_error = "" + recording.provider_delete_error = None recording.save( update_fields=[ "provider_deleted_at", @@ -202,9 +202,9 @@ def delete_local_recording(recording: PhoneCallRecording) -> None: return _full_storage_path(recording.storage_path).unlink(missing_ok=True) - recording.storage_path = "" + recording.storage_path = None recording.byte_size = None - recording.sha256 = "" + recording.sha256 = None recording.local_deleted_at = timezone.now() recording.save( update_fields=[ @@ -224,7 +224,7 @@ def provider_delete_recording(recording: PhoneCallRecording) -> None: client.login() client.delete_recording(recording.provider_recording_id) recording.provider_deleted_at = timezone.now() - recording.provider_delete_error = "" + recording.provider_delete_error = None recording.save( update_fields=["provider_deleted_at", "provider_delete_error", "updated_at"] ) @@ -308,7 +308,7 @@ def assign_phone_number_from_call( call_id: str, company_id: str, person_id: str | None = None, - label: str = "", + label: str | None = None, is_primary: bool = False, ) -> PhoneCallRecord: call_uuid = _uuid_or_client_error(call_id, "Phone call not found") @@ -468,15 +468,18 @@ def _config() -> PhoneProviderConfig: base_url = phone_settings.base_url username = phone_settings.username password = phone_settings.password - # All three are required to log in to the provider portal; failing here - # gives a clear config message instead of an opaque login failure later. - if not base_url or not username or not password: + account_code = phone_settings.account_code + # All four are required to log in and to stamp the records we import; + # failing here gives a clear config message instead of an opaque login + # failure later. + if not base_url or not username or not password or not account_code: missing = [ name for name, value in ( ("base URL", base_url), ("username", username), ("password", password), + ("account code", account_code), ) if not value ] @@ -488,7 +491,7 @@ def _config() -> PhoneProviderConfig: base_url=base_url.rstrip("/"), username=username, password=password, - account_code=phone_settings.account_code, + account_code=account_code, ) @@ -565,7 +568,7 @@ def archive_recording( recording.byte_size = len(content) recording.sha256 = digest recording.archived_at = timezone.now() - recording.archive_error = "" + recording.archive_error = None recording.local_deleted_at = None recording.account_code = call.account_code recording.save( @@ -588,8 +591,10 @@ def archive_recording( @dataclass(frozen=True) class CallClassification: direction: str - our_number: str - external_number: str + # NULL is the only unset: an internal call has no external number, and an + # unattributable one has neither. + our_number: str | None + external_number: str | None origin_endpoint: PhoneEndpoint | None destination_endpoint: PhoneEndpoint | None company: Company | None @@ -605,7 +610,7 @@ class PhoneMatcher: it needs to look up). """ - def __init__(self, numbers: set[str] | None = None): + def __init__(self, numbers: set[str | None] | None = None): self.phone_matches = _build_contact_method_phone_index(numbers) self.endpoints = { endpoint.normalized_number: endpoint @@ -639,7 +644,9 @@ def match_customer(self, *values: str) -> tuple[Company | None, Person | None]: company = Company.objects.get(id=object_id) return company, None - def classify(self, origin: str, destination: str) -> CallClassification: + def classify( + self, origin: str | None, destination: str | None + ) -> CallClassification: normalized_origin = normalize_phone(origin) normalized_destination = normalize_phone(destination) origin_endpoint = self.endpoints.get(normalized_origin) @@ -649,7 +656,7 @@ def classify(self, origin: str, destination: str) -> CallClassification: return CallClassification( direction=PhoneCallRecord.Direction.INTERNAL, our_number=normalized_origin, - external_number="", + external_number=None, origin_endpoint=origin_endpoint, destination_endpoint=destination_endpoint, company=None, @@ -681,7 +688,7 @@ def classify(self, origin: str, destination: str) -> CallClassification: ) company, person = self.match_customer(normalized_origin, normalized_destination) - external_number = "" + external_number = None if normalized_origin and not normalized_destination: external_number = normalized_origin elif normalized_destination and not normalized_origin: @@ -690,7 +697,7 @@ def classify(self, origin: str, destination: str) -> CallClassification: pass # zero or two candidates is not a strict assignable number. return CallClassification( direction=PhoneCallRecord.Direction.UNKNOWN, - our_number="", + our_number=None, external_number=external_number, origin_endpoint=None, destination_endpoint=None, @@ -752,7 +759,7 @@ def rematch_calls_for_numbers(numbers: list[str]) -> None: relevant_numbers = {call.normalized_origin for call in calls} | { call.normalized_destination for call in calls } - relevant_numbers.discard("") + relevant_numbers.discard(None) matcher = PhoneMatcher(numbers=relevant_numbers) for call in calls: classification = matcher.classify(call.origin, call.destination) @@ -796,7 +803,7 @@ def assign_phone_number( phone_number: str, company_id: str, person_id: str | None = None, - label: str = "", + label: str | None = None, is_primary: bool = False, ) -> ContactMethod: normalized = normalize_phone(phone_number) @@ -852,14 +859,14 @@ def assign_phone_number( normalized_value=normalized, defaults={ "value": phone_number.strip(), - "label": label.strip(), + "label": label.strip() if label else None, "is_primary": should_be_primary, "source": ContactMethod.Source.LOCAL, }, ) if not created: method.value = phone_number.strip() - method.label = label.strip() + method.label = label.strip() if label else None method.source = ContactMethod.Source.LOCAL method.is_primary = should_be_primary or method.is_primary method.save( @@ -871,7 +878,7 @@ def assign_phone_number( def _build_contact_method_phone_index( - numbers: set[str] | None = None, + numbers: set[str | None] | None = None, ) -> dict[str, set[tuple[str, str, str]]]: """Index phone methods by normalized number. diff --git a/apps/crm/tests/test_phone_call_service.py b/apps/crm/tests/test_phone_call_service.py index e7b9cdd63..f6fab2859 100644 --- a/apps/crm/tests/test_phone_call_service.py +++ b/apps/crm/tests/test_phone_call_service.py @@ -204,7 +204,7 @@ def test_unknown_call_with_two_parties_has_no_assignable_external_number( classification = PhoneMatcher().classify("021 555 123", "021 555 456") self.assertEqual(classification.direction, PhoneCallRecord.Direction.UNKNOWN) - self.assertEqual(classification.external_number, "") + self.assertIsNone(classification.external_number) def test_unknown_call_with_one_party_keeps_assignable_external_number( self, @@ -301,7 +301,7 @@ def test_deletes_only_downloaded_calls_more_than_one_month_old(self) -> None: old_recording = self._recording(old_call, "old", storage_path="old.mp3") self._recording(recent_call, "recent", storage_path="recent.mp3") - self._recording(not_downloaded_call, "not-downloaded", storage_path="") + self._recording(not_downloaded_call, "not-downloaded", storage_path=None) self._recording( deleted_call, "deleted", @@ -323,7 +323,7 @@ def test_deletes_only_downloaded_calls_more_than_one_month_old(self) -> None: old_recording.refresh_from_db() self.assertIsNotNone(old_recording.provider_deleted_at) - self.assertEqual(old_recording.provider_delete_error, "") + self.assertIsNone(old_recording.provider_delete_error) def _call(self, provider_id: str, call_datetime): return PhoneCallRecord.objects.create( @@ -346,7 +346,7 @@ def _recording( call: PhoneCallRecord, provider_recording_id: str, *, - storage_path: str, + storage_path: str | None, provider_deleted_at=None, ): return PhoneCallRecording.objects.create( @@ -431,6 +431,7 @@ def test_sync_archives_recording_and_is_idempotent(self) -> None: self.assertEqual(recording.filename, "call.mp3") self.assertEqual(recording.byte_size, len(b"recorded audio")) self.assertTrue(recording.sha256) + assert recording.storage_path is not None self.assertTrue( (Path(self.storage_root.name) / recording.storage_path).exists() ) @@ -853,9 +854,9 @@ def test_download_streams_archived_recording_without_provider_settings( "downloads_enabled": False, "recording_deletion_enabled": False, "base_url": None, - "username": "", - "password": "", - "account_code": "", + "username": None, + "password": None, + "account_code": None, }, ) storage_path = "2026/06/02/offline-playback.mp3" diff --git a/apps/crm/views/phone_call_views.py b/apps/crm/views/phone_call_views.py index 0e8edfe85..4574cd145 100644 --- a/apps/crm/views/phone_call_views.py +++ b/apps/crm/views/phone_call_views.py @@ -267,7 +267,7 @@ def list(self, request: Request, *args: str, **kwargs: str) -> Response: queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) calls = list(page if page is not None else queryset) - context = self.get_serializer_context() + context = dict(self.get_serializer_context()) context["phone_recordings_by_call_id"] = { recording.call_id: recording for recording in PhoneCallRecording.objects.filter( diff --git a/apps/job/__init__.py b/apps/job/__init__.py index fdb6157a7..54adbbb90 100644 --- a/apps/job/__init__.py +++ b/apps/job/__init__.py @@ -10,6 +10,8 @@ if apps.ready: from .diff import DiffResult, apply_diff, diff_costset + from .etag import current_job_etag_value, generate_job_etag + from .kanban_version import KanbanDatasetVersion from .mixins import JobLookupMixin, JobNumberLookupMixin from .permissions import IsOfficeStaff, IsStaffUser from .tasks import ( @@ -30,13 +32,16 @@ "JobConfig", "JobLookupMixin", "JobNumberLookupMixin", + "KanbanDatasetVersion", "MetalType", "RDTIType", "SpeedQualityTradeoff", "apply_diff", "auto_archive_completed_jobs_task", "create_job_file_thumbnail_task", + "current_job_etag_value", "diff_costset", + "generate_job_etag", "get_active_jobs", "get_job_folder_path", "request_job_summary_pdf_refresh", diff --git a/apps/job/enums.py b/apps/job/enums.py index a8e85f138..6142ce78c 100644 --- a/apps/job/enums.py +++ b/apps/job/enums.py @@ -34,5 +34,4 @@ class MetalType(models.TextChoices): TITANIUM = "titanium", "Titanium" ZINC = "zinc", "Zinc" GALVANIZED = "galvanized", "Galvanized" - UNSPECIFIED = "unspecified", "Unspecified" OTHER = "other", "Other" diff --git a/apps/job/etag.py b/apps/job/etag.py new file mode 100644 index 000000000..ba5d04831 --- /dev/null +++ b/apps/job/etag.py @@ -0,0 +1,20 @@ +"""ETag utilities for the Job aggregate.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from apps.workflow.etag import generate_updated_at_etag, updated_at_etag_value + +if TYPE_CHECKING: + from apps.job.models import Job + + +def current_job_etag_value(job: Job) -> str: + """Return the full-precision validator value for a Job.""" + return updated_at_etag_value("job", job.id, job.updated_at) + + +def generate_job_etag(job: Job) -> str: + """Return the strong HTTP ETag for a Job.""" + return generate_updated_at_etag("job", job.id, job.updated_at) diff --git a/apps/job/kanban_version.py b/apps/job/kanban_version.py new file mode 100644 index 000000000..896cfc6c9 --- /dev/null +++ b/apps/job/kanban_version.py @@ -0,0 +1,57 @@ +"""Opaque version contract for incremental Kanban reconciliation.""" + +from dataclasses import dataclass +from datetime import UTC, datetime + +from django.utils import timezone + +_EMPTY_TIMESTAMP = datetime(1970, 1, 1, tzinfo=UTC) + + +@dataclass(frozen=True) +class KanbanDatasetVersion: + """Typed representation of the Job dataset version sent to clients.""" + + updated_at: datetime + created_at: datetime + count: int + + @classmethod + def from_values( + cls, + *, + updated_at: datetime | None, + created_at: datetime | None, + count: int, + ) -> "KanbanDatasetVersion": + if count < 0: + raise ValueError("Kanban version count cannot be negative") + if updated_at is not None and timezone.is_naive(updated_at): + raise ValueError("Kanban updated_at version must be timezone-aware") + if created_at is not None and timezone.is_naive(created_at): + raise ValueError("Kanban created_at version must be timezone-aware") + + return cls( + updated_at=(updated_at or _EMPTY_TIMESTAMP).astimezone(UTC), + created_at=(created_at or _EMPTY_TIMESTAMP).astimezone(UTC), + count=count, + ) + + def encode(self) -> str: + """Encode the version for transport as an opaque string.""" + updated = self.updated_at.isoformat(timespec="microseconds") + created = self.created_at.isoformat(timespec="microseconds") + return f"{updated}|{created}|{self.count}" + + @classmethod + def decode(cls, value: str) -> "KanbanDatasetVersion": + """Decode and validate a previously emitted version.""" + try: + updated_value, created_value, count_value = value.split("|") + return cls.from_values( + updated_at=datetime.fromisoformat(updated_value), + created_at=datetime.fromisoformat(created_value), + count=int(count_value), + ) + except (TypeError, ValueError) as exc: + raise ValueError("Invalid Kanban version") from exc diff --git a/apps/job/migrations/0007_forbid_blank_text.py b/apps/job/migrations/0007_forbid_blank_text.py new file mode 100644 index 000000000..e3ada6566 --- /dev/null +++ b/apps/job/migrations/0007_forbid_blank_text.py @@ -0,0 +1,59 @@ +"""Forbid empty strings in nullable text columns (job). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "job_costline": [ + "xero_expense_id", + "xero_time_id" + ], + "job_job": [ + "description", + "notes", + "order_number", + "rdti_type", + "xero_default_task_id", + "xero_project_id" + ], + "job_jobevent": [ + "dedup_hash" + ], + "job_quotespreadsheet": [ + "sheet_url", + "tab" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("job", "0006_rename_job_event_people_company_terms"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/job/migrations/0008_text_unset_is_null.py b/apps/job/migrations/0008_text_unset_is_null.py new file mode 100644 index 000000000..954352b03 --- /dev/null +++ b/apps/job/migrations/0008_text_unset_is_null.py @@ -0,0 +1,82 @@ +"""Give job's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("job", "0007_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="costline", + name="desc", + field=models.CharField( + blank=True, + help_text="Description of this cost line", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="jobdeltarejection", + name="checksum", + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AlterField( + model_name="jobdeltarejection", + name="detail", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="jobdeltarejection", + name="request_etag", + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AlterField( + model_name="jobevent", + name="delta_checksum", + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AlterField( + model_name="jobfile", + name="mime_type", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.RunSQL( + sql='UPDATE job_costline SET "desc" = NULL WHERE "desc" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE job_jobevent SET "delta_checksum" = NULL WHERE "delta_checksum" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE job_jobdeltarejection SET "detail" = NULL WHERE "detail" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE job_jobdeltarejection SET "checksum" = NULL WHERE "checksum" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE job_jobdeltarejection SET "request_etag" = NULL WHERE "request_etag" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE job_jobfile SET "mime_type" = NULL WHERE "mime_type" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/job/migrations/0009_text_unset_constraints.py b/apps/job/migrations/0009_text_unset_constraints.py new file mode 100644 index 000000000..10b36a19f --- /dev/null +++ b/apps/job/migrations/0009_text_unset_constraints.py @@ -0,0 +1,45 @@ +"""Add the not-blank CHECK constraints for job's newly nullable columns. + +Separate from 0008_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "job_costline": [ + "desc" + ], + "job_jobdeltarejection": [ + "checksum", + "detail", + "request_etag" + ], + "job_jobevent": [ + "delta_checksum" + ], + "job_jobfile": [ + "mime_type" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("job", "0008_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/job/migrations/0010_job_completion_checklist_fields.py b/apps/job/migrations/0010_job_completion_checklist_fields.py new file mode 100644 index 000000000..99b380680 --- /dev/null +++ b/apps/job/migrations/0010_job_completion_checklist_fields.py @@ -0,0 +1,53 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + """Front-desk completion checklist as Job fields. + + ``collected`` is renamed rather than replaced: it meant the same thing and was + never writable (no serializer, endpoint, admin or UI ever set it), so every + row holds the default and the rename carries no stale data forward. + """ + + dependencies = [ + ("job", "0009_text_unset_constraints"), + ] + + operations = [ + migrations.RenameField( + model_name="job", + old_name="collected", + new_name="released", + ), + migrations.AlterField( + model_name="job", + name="released", + field=models.BooleanField( + default=False, + help_text=( + "The job has left our possession, by collection, delivery or " + "on-site install." + ), + ), + ), + migrations.AddField( + model_name="job", + name="foreman_signed_off", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="job", + name="timesheets_collected", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="job", + name="materials_checked", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="job", + name="customer_called", + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/job/models/costing.py b/apps/job/models/costing.py index 15fd011dd..de1218eed 100644 --- a/apps/job/models/costing.py +++ b/apps/job/models/costing.py @@ -181,7 +181,7 @@ class CostLine(models.Model): ) kind = models.CharField(max_length=20, choices=KIND_CHOICES) desc = models.CharField( - max_length=255, help_text="Description of this cost line", blank=True + max_length=255, help_text="Description of this cost line", blank=True, null=True ) quantity = models.DecimalField( max_digits=10, decimal_places=3, default=Decimal("1.000") diff --git a/apps/job/models/job.py b/apps/job/models/job.py index a0cd3ee6d..aff619284 100644 --- a/apps/job/models/job.py +++ b/apps/job/models/job.py @@ -77,6 +77,10 @@ def untracked_update(self, **kwargs): bookkeeping fields that are in UNTRACKED_FIELDS.""" return models.QuerySet.update(self, **kwargs) + def touch_updated_at(self, *, at: datetime) -> int: + """Advance only the aggregate freshness timestamp.""" + return models.QuerySet.update(self, updated_at=at) + JobManager = models.Manager.from_queryset(JobQuerySet) @@ -150,6 +154,17 @@ class Job(models.Model): "is_urgent", ] + # The front-desk completion checklist, in display order. The Finish Job + # serializers derive their field lists from this, so an item is declared + # once. + COMPLETION_CHECKLIST_FIELDS = [ + "foreman_signed_off", + "timesheets_collected", + "materials_checked", + "customer_called", + "released", + ] + # Fields where changes are NOT audited via JobEvent. Every field NOT in # this set is automatically tracked — adding a new business field to Job # gives you audit coverage for free. @@ -303,8 +318,19 @@ class Job(models.Model): # Shop job has no company (company_id is None) job_is_valid = models.BooleanField(default=False) - collected: bool = models.BooleanField(default=False) paid: bool = models.BooleanField(default=False) + + # Front-desk completion checklist. Each is a fact only a person knows, so + # none of them can be derived. Ticking one writes a job-history event through + # _FIELD_HANDLERS below; nothing here blocks invoicing or moves the job. + foreman_signed_off = models.BooleanField(default=False) + timesheets_collected = models.BooleanField(default=False) + materials_checked = models.BooleanField(default=False) + customer_called = models.BooleanField(default=False) + released = models.BooleanField( + default=False, + help_text="The job has left our possession, by collection, delivery or on-site install.", + ) fully_invoiced: bool = models.BooleanField( default=False, help_text="The total value of invoices for this job matches the total value of the job.", @@ -851,7 +877,7 @@ def _record_change_event( schema_version=enrichment.get("schema_version", 0), change_id=enrichment.get("change_id"), delta_meta=enrichment.get("delta_meta"), - delta_checksum=enrichment.get("delta_checksum", ""), + delta_checksum=enrichment.get("delta_checksum") or None, ) def _apply_change_side_effects(self, changes_before, changes_after): @@ -1142,10 +1168,30 @@ def _handle_rdti_type_change(_self_job, old_type, new_type): "payment_updated", "Paid", ), - "collected": _handle_boolean_change( - "job_collected", - "collection_updated", - "Collected", + "foreman_signed_off": _handle_boolean_change( + "completion_checklist_updated", + "completion_checklist_updated", + "Foreman sign-off", + ), + "timesheets_collected": _handle_boolean_change( + "completion_checklist_updated", + "completion_checklist_updated", + "Timesheets collected", + ), + "materials_checked": _handle_boolean_change( + "completion_checklist_updated", + "completion_checklist_updated", + "Materials checked", + ), + "customer_called": _handle_boolean_change( + "completion_checklist_updated", + "completion_checklist_updated", + "Customer called", + ), + "released": _handle_boolean_change( + "completion_checklist_updated", + "completion_checklist_updated", + "Job released", ), "complex_job": _handle_boolean_change( "job_updated", diff --git a/apps/job/models/job_delta_rejection.py b/apps/job/models/job_delta_rejection.py index e9cd06ce0..32603b173 100644 --- a/apps/job/models/job_delta_rejection.py +++ b/apps/job/models/job_delta_rejection.py @@ -24,10 +24,10 @@ class JobDeltaRejection(models.Model): ) change_id = models.UUIDField(null=True, blank=True, db_index=True) reason = models.CharField(max_length=255) - detail = models.TextField(blank=True) + detail = models.TextField(blank=True, null=True) envelope = models.JSONField() - checksum = models.CharField(max_length=128, blank=True) - request_etag = models.CharField(max_length=128, blank=True) + checksum = models.CharField(max_length=128, blank=True, null=True) + request_etag = models.CharField(max_length=128, blank=True, null=True) request_ip = models.GenericIPAddressField(null=True, blank=True) created_at = models.DateTimeField(default=timezone.now, editable=False) diff --git a/apps/job/models/job_event.py b/apps/job/models/job_event.py index bcefae311..32fa55e00 100644 --- a/apps/job/models/job_event.py +++ b/apps/job/models/job_event.py @@ -1,5 +1,6 @@ import hashlib import uuid +from collections.abc import Callable from datetime import timedelta from django.core.exceptions import ValidationError @@ -47,6 +48,25 @@ def _truncate_change(label: str, old, new) -> str: return f"{label} changed from '{_truncate(old)}' to '{_truncate(new)}'" +def _completion_confirmation_descriptor( + confirmed: str, withdrawn: str +) -> Callable[[str, str], str]: + """Descriptor factory for front-desk checklist items. + + Both wordings are given explicitly rather than built from one subject: an + unticking is the change most worth finding later, so it earns a sentence that + reads properly instead of a negated one that does not. + """ + + def descriptor(old: str, new: str) -> str: + if _truthy(new): + return confirmed + else: + return withdrawn + + return descriptor + + def _quote_acceptance_descriptor(old, new) -> str: if new and not old: return f"Quote accepted on {new}" @@ -65,10 +85,22 @@ def _quote_acceptance_descriptor(old, new) -> str: "Marked as complex job" if _truthy(new) else "Unmarked as complex job" ), "Paid": lambda old, new: ("Marked as paid" if _truthy(new) else "Marked as unpaid"), - "Collected": lambda old, new: ( - "Marked as collected" if _truthy(new) else "Marked as not collected" - ), "Quote acceptance date": _quote_acceptance_descriptor, + "Foreman sign-off": _completion_confirmation_descriptor( + "Foreman signed the job off", "Foreman sign-off withdrawn" + ), + "Timesheets collected": _completion_confirmation_descriptor( + "Timesheets collected from the workshop", "Timesheet collection unconfirmed" + ), + "Materials checked": _completion_confirmation_descriptor( + "Materials checked on the job", "Materials check unconfirmed" + ), + "Customer called": _completion_confirmation_descriptor( + "Customer called", "Customer call unconfirmed" + ), + "Job released": _completion_confirmation_descriptor( + "Job released", "Job release withdrawn" + ), "Internal notes": lambda old, new: _truncate_change("Notes", old, new), "Job description": lambda old, new: _truncate_change("Description", old, new), "Notes": lambda old, new: _truncate_change("Notes", old, new), @@ -141,7 +173,7 @@ class JobEvent(models.Model): delta_before = models.JSONField(null=True, blank=True) delta_after = models.JSONField(null=True, blank=True) delta_meta = models.JSONField(null=True, blank=True) - delta_checksum = models.CharField(max_length=128, blank=True, default="") + delta_checksum = models.CharField(max_length=128, blank=True, null=True) detail = models.JSONField( default=dict, @@ -324,28 +356,29 @@ def _build_jsa_description(detail: dict) -> str: return f"JSA generated: {title}" _DESCRIPTION_BUILDERS = { - "job_created": _build_job_created_description.__func__, - "status_changed": _build_status_changed_description.__func__, - "job_updated": _build_changes_description.__func__, - "company_changed": _build_changes_description.__func__, - "person_changed": _build_changes_description.__func__, - "notes_updated": _build_changes_description.__func__, - "delivery_date_changed": _build_changes_description.__func__, - "quote_accepted": _build_changes_description.__func__, - "pricing_changed": _build_changes_description.__func__, - "priority_changed": _build_priority_changed_description.__func__, - "payment_received": _build_changes_description.__func__, - "payment_updated": _build_changes_description.__func__, - "job_collected": _build_changes_description.__func__, - "collection_updated": _build_changes_description.__func__, - "job_rejected": _build_changes_description.__func__, - "manual_note": _build_manual_note_description.__func__, - "invoice_created": _build_invoice_created_description.__func__, - "invoice_deleted": _build_invoice_deleted_description.__func__, - "quote_created": _build_quote_created_description.__func__, - "quote_deleted": _build_quote_deleted_description.__func__, - "delivery_docket_generated": _build_delivery_docket_description.__func__, - "jsa_generated": _build_jsa_description.__func__, + "job_created": _build_job_created_description, + "status_changed": _build_status_changed_description, + "job_updated": _build_changes_description, + "company_changed": _build_changes_description, + "person_changed": _build_changes_description, + "notes_updated": _build_changes_description, + "delivery_date_changed": _build_changes_description, + "quote_accepted": _build_changes_description, + "pricing_changed": _build_changes_description, + "priority_changed": _build_priority_changed_description, + "payment_received": _build_changes_description, + "payment_updated": _build_changes_description, + "job_collected": _build_changes_description, + "collection_updated": _build_changes_description, + "job_rejected": _build_changes_description, + "completion_checklist_updated": _build_changes_description, + "manual_note": _build_manual_note_description, + "invoice_created": _build_invoice_created_description, + "invoice_deleted": _build_invoice_deleted_description, + "quote_created": _build_quote_created_description, + "quote_deleted": _build_quote_deleted_description, + "delivery_docket_generated": _build_delivery_docket_description, + "jsa_generated": _build_jsa_description, } class Meta: diff --git a/apps/job/models/job_file.py b/apps/job/models/job_file.py index d785e3bfc..ea796938f 100644 --- a/apps/job/models/job_file.py +++ b/apps/job/models/job_file.py @@ -46,7 +46,7 @@ class JobFile(models.Model): job = models.ForeignKey("Job", related_name="files", on_delete=models.CASCADE) filename = models.CharField(max_length=255) file_path = models.CharField(max_length=500) - mime_type = models.CharField(max_length=100, blank=True) + mime_type = models.CharField(max_length=100, blank=True, null=True) uploaded_at = models.DateTimeField(auto_now_add=True) status = models.CharField( max_length=20, diff --git a/apps/job/serializers/__init__.py b/apps/job/serializers/__init__.py index 701c22210..f8c52bb8f 100644 --- a/apps/job/serializers/__init__.py +++ b/apps/job/serializers/__init__.py @@ -82,12 +82,16 @@ AssignJobSerializer, CompanyDefaultsJobDetailSerializer, CompleteJobSerializer, + FinishJobPayload, + FinishJobSummarySerializer, GroupedJobDeltaRejectionListResponseSerializer, GroupedJobDeltaRejectionResolveRequestSerializer, GroupedJobDeltaRejectionResolveResponseSerializer, GroupedJobDeltaRejectionSerializer, InvoiceSerializer, JobBasicInformationResponseSerializer, + JobCompletionChecklistSerializer, + JobCompletionChecklistUpdateSerializer, JobCostSetSummarySerializer, JobCostSummaryResponseSerializer, JobCreateResponseSerializer, @@ -102,6 +106,7 @@ JobEventCreateSerializer, JobEventSerializer, JobEventsResponseSerializer, + JobFinishResponseSerializer, JobHeaderResponseSerializer, JobInvoicesResponseSerializer, JobPatchSerializer, @@ -146,6 +151,7 @@ JobReorderSerializer, JobSearchFiltersSerializer, JobStatusUpdateSerializer, + KanbanChangesResponseSerializer, KanbanColumnJobSerializer, KanbanErrorResponseSerializer, KanbanJobPersonSerializer, @@ -223,12 +229,16 @@ "FetchJobsResponseSerializer", "FetchStatusValuesResponseSerializer", "FiltersAppliedSerializer", + "FinishJobPayload", + "FinishJobSummarySerializer", "GroupedJobDeltaRejectionListResponseSerializer", "GroupedJobDeltaRejectionResolveRequestSerializer", "GroupedJobDeltaRejectionResolveResponseSerializer", "GroupedJobDeltaRejectionSerializer", "InvoiceSerializer", "JobBasicInformationResponseSerializer", + "JobCompletionChecklistSerializer", + "JobCompletionChecklistUpdateSerializer", "JobCostSetSummarySerializer", "JobCostSummaryResponseSerializer", "JobCreateResponseSerializer", @@ -251,6 +261,7 @@ "JobFileUploadSerializer", "JobFileUploadSuccessResponseSerializer", "JobFileUploadViewResponseSerializer", + "JobFinishResponseSerializer", "JobHeaderResponseSerializer", "JobInvoicesResponseSerializer", "JobLabourRateSerializer", @@ -284,6 +295,7 @@ "JobSummarySerializer", "JobTimelineResponseSerializer", "JobUndoSerializer", + "KanbanChangesResponseSerializer", "KanbanColumnJobSerializer", "KanbanErrorResponseSerializer", "KanbanJobPersonSerializer", diff --git a/apps/job/serializers/costing_serializer.py b/apps/job/serializers/costing_serializer.py index 474dfb7df..415404606 100644 --- a/apps/job/serializers/costing_serializer.py +++ b/apps/job/serializers/costing_serializer.py @@ -1,6 +1,6 @@ import logging from decimal import Decimal -from typing import Any, cast +from typing import Any from drf_spectacular.utils import extend_schema_field from rest_framework import serializers @@ -16,11 +16,12 @@ resolve_xero_pay_item_for_job, ) from apps.workflow.models import CompanyDefaults +from apps.workflow.serializers_base import NullUnsetModelSerializer logger = logging.getLogger(__name__) -class CostLineSerializer(serializers.ModelSerializer): +class CostLineSerializer(NullUnsetModelSerializer[CostLine]): """ Serializer for CostLine model - read-only with basic depth """ @@ -33,7 +34,7 @@ class Meta: fields = CostLine.COSTLINE_API_FIELDS + ["total_cost", "total_rev"] -class TimesheetCostLineSerializer(serializers.ModelSerializer): +class TimesheetCostLineSerializer(NullUnsetModelSerializer[CostLine]): """ Serializer for CostLine model specifically for timesheet entries @@ -133,7 +134,7 @@ class Meta: read_only_fields = fields -class CostLineCreateUpdateSerializer(serializers.ModelSerializer): +class CostLineCreateUpdateSerializer(NullUnsetModelSerializer[CostLine]): """ Serializer for CostLine creation and updates - full write capabilities """ @@ -308,7 +309,7 @@ def save(self, **kwargs: Any) -> CostLine: logger.error(f"Error calculating unit_cost: {e}") raise - return cast(CostLine, super().save(**kwargs)) + return super().save(**kwargs) def create(self, validated_data): """Override create to define line approval automatically""" @@ -344,7 +345,7 @@ def get_profitMargin(self, obj) -> float: return 0.0 -class CostSetSerializer(serializers.ModelSerializer): +class CostSetSerializer(NullUnsetModelSerializer[CostSet]): """ Serializer for CostSet model - includes nested cost lines """ diff --git a/apps/job/serializers/job_file_serializer.py b/apps/job/serializers/job_file_serializer.py index 5938ec3b1..d2211eb12 100644 --- a/apps/job/serializers/job_file_serializer.py +++ b/apps/job/serializers/job_file_serializer.py @@ -2,9 +2,10 @@ from rest_framework import serializers from apps.job.models import JobFile +from apps.workflow.serializers_base import NullUnsetModelSerializer -class JobFileSerializer(serializers.ModelSerializer): +class JobFileSerializer(NullUnsetModelSerializer[JobFile]): # force DRF to treat `id` as an input field, and require it id = serializers.UUIDField(required=True, allow_null=False) diff --git a/apps/job/serializers/job_quote_chat_serializer.py b/apps/job/serializers/job_quote_chat_serializer.py index 159e21ab8..c7fb020fd 100644 --- a/apps/job/serializers/job_quote_chat_serializer.py +++ b/apps/job/serializers/job_quote_chat_serializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from apps.job.models import JobQuoteChat +from apps.workflow.serializers_base import NullUnsetModelSerializer -class JobQuoteChatCreateSerializer(serializers.ModelSerializer): +class JobQuoteChatCreateSerializer(NullUnsetModelSerializer[JobQuoteChat]): """ Serializer for creating new JobQuoteChat messages. Validates required fields and business rules. @@ -32,7 +33,7 @@ def validate_message_id(self, value): return value -class JobQuoteChatSerializer(serializers.ModelSerializer): +class JobQuoteChatSerializer(NullUnsetModelSerializer[JobQuoteChat]): """ Serializer for JobQuoteChat responses (includes timestamp). Used when returning saved messages to the client. @@ -47,7 +48,7 @@ class Meta: } -class JobQuoteChatUpdateSerializer(serializers.ModelSerializer): +class JobQuoteChatUpdateSerializer(NullUnsetModelSerializer[JobQuoteChat]): """ Serializer for updating existing JobQuoteChat messages. Used for PATCH operations, especially streaming response updates. diff --git a/apps/job/serializers/job_serializer.py b/apps/job/serializers/job_serializer.py index c50a8f724..e6ef86c9b 100644 --- a/apps/job/serializers/job_serializer.py +++ b/apps/job/serializers/job_serializer.py @@ -1,6 +1,6 @@ import json import logging -from typing import Any, Optional +from typing import Any, Optional, TypedDict from django.core.exceptions import ObjectDoesNotExist from drf_spectacular.utils import extend_schema_field @@ -8,9 +8,12 @@ from apps.accounting.models.invoice import Invoice from apps.accounting.models.quote import Quote +from apps.accounting.services.finish_job_summary import FinishJobSummary from apps.company.models import Company, Person from apps.job.models import Job, JobEvent, JobFile from apps.workflow.models import XeroPayItem +from apps.workflow.serializers import AppErrorResponseSerializer +from apps.workflow.serializers_base import NullUnsetModelSerializer from .costing_serializer import ( CostSetSerializer, @@ -24,7 +27,7 @@ DEBUG_SERIALIZER = False -class InvoiceSerializer(serializers.ModelSerializer): +class InvoiceSerializer(NullUnsetModelSerializer[Invoice]): total_excl_tax = serializers.FloatField() total_incl_tax = serializers.FloatField() amount_due = serializers.FloatField() @@ -47,7 +50,7 @@ class Meta: ] -class QuoteSerializer(serializers.ModelSerializer): +class QuoteSerializer(NullUnsetModelSerializer[Quote]): total_excl_tax = serializers.FloatField() total_incl_tax = serializers.FloatField() @@ -67,7 +70,7 @@ class Meta: # Front-end uses "quote" field with financial data being displayed. # This is useless for front-end currently, we use all information from above. # Since I didn't get the specific requirements for this, I'll just ignore and keep using the original serializer. -class XeroQuoteSerializer(serializers.ModelSerializer): +class XeroQuoteSerializer(NullUnsetModelSerializer[Quote]): """Simplified Quote serializer for xero_quote field""" class Meta: @@ -76,7 +79,7 @@ class Meta: # Same for this. -class XeroInvoiceSerializer(serializers.ModelSerializer): +class XeroInvoiceSerializer(NullUnsetModelSerializer[Invoice]): """Simplified Invoice serializer for xero_invoices field""" class Meta: @@ -94,7 +97,7 @@ class CompanyDefaultsJobDetailSerializer(serializers.Serializer): wage_rate = serializers.FloatField(help_text="Default wage rate for staff") -class JobSerializer(serializers.ModelSerializer): +class JobSerializer(NullUnsetModelSerializer[Job]): # Per-subtype charge-out rates deliberately NOT nested here: nothing reads # them from the job payload, and serializing them lazy-loads # JobLabourRate.labour_subtype (the dev/E2E n+1 guard raises). Rates are @@ -344,7 +347,7 @@ class JobQuoteAcceptanceSerializer(serializers.Serializer): message = serializers.CharField() -class JobEventSerializer(serializers.ModelSerializer): +class JobEventSerializer(NullUnsetModelSerializer[JobEvent]): """Serializer for JobEvent model - read-only for frontend consumption Enhanced to expose complete delta envelope data for undo operations. @@ -367,7 +370,7 @@ class JobEventSerializer(serializers.ModelSerializer): delta_before = serializers.JSONField(read_only=True, allow_null=True) delta_after = serializers.JSONField(read_only=True, allow_null=True) delta_meta = serializers.JSONField(read_only=True, allow_null=True) - delta_checksum = serializers.CharField(read_only=True, allow_blank=True) + delta_checksum = serializers.CharField(read_only=True, allow_null=True) # Enhanced fields for undo support can_undo = serializers.SerializerMethodField( @@ -470,7 +473,7 @@ class JobSummaryResponseSerializer(serializers.Serializer): data = JobSummaryDataSerializer() -class CompleteJobSerializer(serializers.ModelSerializer): +class CompleteJobSerializer(NullUnsetModelSerializer[Job]): company_name = serializers.CharField(source="company.name", read_only=True) job_status = serializers.CharField(source="status") @@ -531,11 +534,9 @@ class JobDetailResponseSerializer(serializers.Serializer): data = JobDataSerializer() -class JobRestErrorResponseSerializer(serializers.Serializer): +class JobRestErrorResponseSerializer(AppErrorResponseSerializer): """Serializer for job REST error responses.""" - error = serializers.CharField() - class JobDeleteResponseSerializer(serializers.Serializer): """Serializer for job deletion response.""" @@ -779,7 +780,7 @@ class WeeklyMetricsSerializer(serializers.Serializer): # JobView Enhancement Serializers -class JobHeaderResponseSerializer(serializers.ModelSerializer): +class JobHeaderResponseSerializer(NullUnsetModelSerializer[Job]): """Serializer for job header response - essential job data for fast loading.""" job_id = serializers.UUIDField(source="id") @@ -847,6 +848,88 @@ class JobCostSummaryResponseSerializer(serializers.Serializer): actual = JobCostSetSummarySerializer(allow_null=True) +class FinishJobPayload(TypedDict): + """What the Finish Job endpoint serializes: a balance plus a checklist.""" + + summary: FinishJobSummary + checklist: Job + + +class FinishJobSummarySerializer(serializers.Serializer[FinishJobSummary]): + """The authoritative customer balance shown in the Finish Job workspace. + + Read-only: every value is calculated by + apps.accounting.services.finish_job_summary, and the frontend formats rather + than recomputes them (ADR 0020). + """ + + job_value_excl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + valid_invoiced_excl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + outstanding_invoiced_incl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + remaining_to_invoice_excl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + remaining_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + remaining_to_invoice_incl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + total_to_pay_incl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + over_invoiced_excl_gst = serializers.DecimalField( + max_digits=12, decimal_places=2, read_only=True + ) + + +class JobCompletionChecklistSerializer(NullUnsetModelSerializer[Job]): + """Read shape for the front-desk completion checklist. + + The items are Job fields, so each tick is audited by the job's own + field-change machinery. Who ticked what, and when, is in the job history. + """ + + class Meta: + model = Job + fields = Job.COMPLETION_CHECKLIST_FIELDS + read_only_fields = fields + + +class JobCompletionChecklistUpdateSerializer(NullUnsetModelSerializer[Job]): + """Partial update shape: send only the items being changed. + + Unknown keys are rejected rather than dropped, so a client typo is a 400 + instead of a silent no-op. + """ + + class Meta: + model = Job + fields = Job.COMPLETION_CHECKLIST_FIELDS + extra_kwargs = {field: {"required": False} for field in fields} + + def validate(self, attrs: dict[str, bool]) -> dict[str, bool]: + unknown = sorted(set(self.initial_data) - set(self.fields)) + if unknown: + raise serializers.ValidationError( + f"Unknown checklist item(s): {', '.join(unknown)}." + ) + return attrs + + +class JobFinishResponseSerializer(serializers.Serializer[FinishJobPayload]): + """Everything the Finish Job workspace reads in one request.""" + + summary = FinishJobSummarySerializer(read_only=True) + checklist = JobCompletionChecklistSerializer(read_only=True) + + class JobEventsResponseSerializer(serializers.Serializer): """Serializer for job events response""" @@ -923,9 +1006,7 @@ class TimelineEntrySerializer(serializers.Serializer): delta_before = serializers.JSONField(required=False, allow_null=True) delta_after = serializers.JSONField(required=False, allow_null=True) delta_meta = serializers.JSONField(required=False, allow_null=True) - delta_checksum = serializers.CharField( - required=False, allow_blank=True, allow_null=True - ) + delta_checksum = serializers.CharField(required=False, allow_null=True) # CostLine specific fields cost_set_kind = serializers.CharField(required=False, allow_null=True) diff --git a/apps/job/serializers/kanban_serializer.py b/apps/job/serializers/kanban_serializer.py index 92e114fcb..a4edf863e 100644 --- a/apps/job/serializers/kanban_serializer.py +++ b/apps/job/serializers/kanban_serializer.py @@ -4,6 +4,8 @@ from rest_framework import serializers +from apps.workflow.serializers import AppErrorResponseSerializer + class JobReorderSerializer(serializers.Serializer): """Serializer for job reorder request data.""" @@ -56,11 +58,10 @@ class KanbanSuccessResponseSerializer(serializers.Serializer): message = serializers.CharField() -class KanbanErrorResponseSerializer(serializers.Serializer): +class KanbanErrorResponseSerializer(AppErrorResponseSerializer): """Serializer for error kanban operation response.""" success = serializers.BooleanField(default=False) - error = serializers.CharField() class JobSearchFiltersSerializer(serializers.Serializer): @@ -263,6 +264,16 @@ class FetchJobsByColumnResponseSerializer(serializers.Serializer): error = serializers.CharField(required=False) +class KanbanChangesResponseSerializer(serializers.Serializer): + """Serializer for incremental Kanban freshness reconciliation.""" + + success = serializers.BooleanField() + jobs = KanbanColumnJobSerializer(many=True) + removed_job_ids = serializers.ListField(child=serializers.UUIDField()) + full_refresh_required = serializers.BooleanField() + error = serializers.CharField(required=False) + + class WorkshopJobSerializer(serializers.Serializer): id = serializers.UUIDField() name = serializers.CharField() diff --git a/apps/job/serializers/labour_serializer.py b/apps/job/serializers/labour_serializer.py index b9efccecd..61d55ba26 100644 --- a/apps/job/serializers/labour_serializer.py +++ b/apps/job/serializers/labour_serializer.py @@ -4,9 +4,10 @@ from rest_framework import serializers from apps.job.models import JobLabourRate, LabourSubtype +from apps.workflow.serializers_base import NullUnsetModelSerializer -class LabourSubtypeSerializer(serializers.ModelSerializer[LabourSubtype]): +class LabourSubtypeSerializer(NullUnsetModelSerializer[LabourSubtype]): """Read serializer for labour subtypes (dropdowns, rate displays).""" default_charge_out_rate = serializers.DecimalField( @@ -30,7 +31,7 @@ class Meta: read_only_fields = fields -class LabourSubtypeManageSerializer(serializers.ModelSerializer[LabourSubtype]): +class LabourSubtypeManageSerializer(NullUnsetModelSerializer[LabourSubtype]): """Read/write serializer for the company labour-subtype management UI.""" default_charge_out_rate = serializers.DecimalField( @@ -114,7 +115,7 @@ def update( return super().update(instance, validated_data) -class JobLabourRateSerializer(serializers.ModelSerializer[JobLabourRate]): +class JobLabourRateSerializer(NullUnsetModelSerializer[JobLabourRate]): """A job's charge-out rate for one labour subtype.""" charge_out_rate = serializers.DecimalField( diff --git a/apps/job/serializers/quote_spreadsheet_serializer.py b/apps/job/serializers/quote_spreadsheet_serializer.py index 14cc6ba4e..96907c55c 100644 --- a/apps/job/serializers/quote_spreadsheet_serializer.py +++ b/apps/job/serializers/quote_spreadsheet_serializer.py @@ -8,9 +8,10 @@ from rest_framework import serializers from apps.job.models.spreadsheet import QuoteSpreadsheet +from apps.workflow.serializers_base import NullUnsetModelSerializer -class QuoteSpreadsheetSerializer(serializers.ModelSerializer): +class QuoteSpreadsheetSerializer(NullUnsetModelSerializer[QuoteSpreadsheet]): """ Serializer for QuoteSpreadsheet model. diff --git a/apps/job/serializers/quote_sync_serializer.py b/apps/job/serializers/quote_sync_serializer.py index ff19e7aa9..062c2414b 100644 --- a/apps/job/serializers/quote_sync_serializer.py +++ b/apps/job/serializers/quote_sync_serializer.py @@ -34,7 +34,7 @@ class QuoteSyncErrorResponseSerializer(serializers.Serializer): class LinkQuoteSheetSerializer(serializers.Serializer): """Serializer for link quote sheet request data.""" - template_url = serializers.URLField(required=False, allow_blank=True) + template_url = serializers.URLField(required=False, allow_null=True) class LinkQuoteSheetResponseSerializer(serializers.Serializer): diff --git a/apps/job/services/__init__.py b/apps/job/services/__init__.py index fc06cea8e..95517dbb3 100644 --- a/apps/job/services/__init__.py +++ b/apps/job/services/__init__.py @@ -36,7 +36,6 @@ DeltaValidationError, JobDeltaPayload, JobRestService, - PreconditionFailed, ) from .job_service import ( JobStaffService, @@ -44,13 +43,18 @@ get_job_total_value, get_paid_complete_jobs, recalculate_job_invoicing_state, + update_completion_checklist, ) from .job_summary_pdf_service import JobSummaryPdfService from .kanban_categorization_service import ( KanbanCategorizationService, KanbanColumn, ) - from .kanban_service import KanbanSerializationContext, KanbanService + from .kanban_service import ( + KanbanChanges, + KanbanSerializationContext, + KanbanService, + ) from .labour_subtype_service import seed_subtype_onto_existing_jobs from .mcp_chat_service import MCPChatService from .month_end_service import MonthEndService @@ -116,6 +120,7 @@ "JobStaffService", "JobSummaryPdfService", "KanbanCategorizationService", + "KanbanChanges", "KanbanColumn", "KanbanSerializationContext", "KanbanService", @@ -123,7 +128,6 @@ "MonthEndService", "PaidFlagResult", "PaidFlagService", - "PreconditionFailed", "QuoteImportError", "QuoteImportResult", "QuoteModeController", @@ -182,5 +186,6 @@ "serialize_validation_report", "sync_job_folder", "to_decimal", + "update_completion_checklist", "wait_until_file_ready", ] diff --git a/apps/job/services/file_service.py b/apps/job/services/file_service.py index e30570598..1f4deca0e 100644 --- a/apps/job/services/file_service.py +++ b/apps/job/services/file_service.py @@ -82,7 +82,7 @@ def sync_job_folder(job): job=job, filename=filename, file_path=relative_path, - mime_type=mime_type or "", + mime_type=mime_type or None, ) # Generate thumbnail if needed diff --git a/apps/job/services/job_rest_service.py b/apps/job/services/job_rest_service.py index 3e4a74ab0..4b9cabca6 100644 --- a/apps/job/services/job_rest_service.py +++ b/apps/job/services/job_rest_service.py @@ -22,6 +22,7 @@ from apps.accounts.models import Staff from apps.company.models import Company, Person +from apps.job.etag import current_job_etag_value, generate_job_etag from apps.job.models import Job, JobDeltaRejection, JobEvent, LabourSubtype from apps.job.models.costing import CostLine from apps.job.serializers import JobSerializer @@ -33,17 +34,15 @@ QuoteSerializer, ) from apps.job.services.delta_checksum import compute_job_delta_checksum, normalise_value +from apps.workflow.etag import if_match_satisfied +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.models import CompanyDefaults, XeroPayItem from apps.workflow.services.error_persistence import persist_app_error logger = logging.getLogger(__name__) -class PreconditionFailed(Exception): - """Raised when ETag precondition fails (HTTP 412).""" - - -class DeltaValidationError(PreconditionFailed): +class DeltaValidationError(PreconditionFailedError): """Raised when the delta payload fails checksum or before-state validation.""" def __init__( @@ -58,18 +57,6 @@ def __init__( self.server_checksum = server_checksum -def _current_job_etag_value(job: Job) -> str: - """ - Return normalized ETag value for comparison (without W/ and quotes). - Mirrors BaseJobRestView._gen_job_etag but normalized. - """ - try: - ts_ms = int(job.updated_at.timestamp() * 1000) - except Exception: - ts_ms = 0 - return f"job:{job.id}:{ts_ms}" - - @dataclass class JobDeltaPayload: """ @@ -448,8 +435,8 @@ def _record_delta_rejection( "reason": (reason or "")[:255], "detail": JobRestService._serialise_detail(detail), "envelope": _to_json_safe(envelope or {}), - "checksum": checksum or "", - "request_etag": (request_etag or "")[:128], + "checksum": checksum or None, + "request_etag": (request_etag or None) and request_etag[:128], "request_ip": request_ip, } @@ -510,9 +497,9 @@ def _collect_soft_fail_context( return None @staticmethod - def _serialise_detail(detail: Any) -> str: + def _serialise_detail(detail: Any) -> str | None: if detail in (None, ""): - return "" + return None converted = _to_json_safe(detail) if isinstance(converted, (dict, list)): return json.dumps(converted) @@ -990,7 +977,7 @@ def mark_job_delta_rejection_group_unresolved_by_fingerprint( @staticmethod def update_job( - job_id: UUID, data: Dict[str, Any], user: Staff, if_match: str | None = None + job_id: UUID, data: Dict[str, Any], user: Staff, if_match: str ) -> Job: """ Updates an existing Job with optimistic concurrency control (ETag). @@ -1060,20 +1047,15 @@ def update_job( # Concurrency check using normalized ETag value - AFTER delta validation logger.debug("[JOB_UPDATE] Checking ETag precondition...") - if not if_match: - logger.warning( - "[JOB_UPDATE] No If-Match header provided - skipping ETag validation" + current_etag = generate_job_etag(job) + logger.debug(f"[JOB_UPDATE] Current ETag: {current_etag}") + logger.debug(f"[JOB_UPDATE] Company ETag: {if_match}") + if not if_match_satisfied(if_match, current_etag): + logger.error( + f"[JOB_UPDATE] ETag mismatch! Current: {current_etag}, Expected: {if_match}" ) - else: - current_norm = _current_job_etag_value(job) - logger.debug(f"[JOB_UPDATE] Current ETag: {current_norm}") - logger.debug(f"[JOB_UPDATE] Company ETag: {if_match}") - if current_norm != if_match: - logger.error( - f"[JOB_UPDATE] ETag mismatch! Current: {current_norm}, Expected: {if_match}" - ) - raise PreconditionFailed("ETag mismatch: resource has changed") - logger.debug("[JOB_UPDATE] ETag validation passed") + raise PreconditionFailedError("ETag mismatch: resource has changed") + logger.debug("[JOB_UPDATE] ETag validation passed") # DEBUG: Log incoming data logger.debug(f"JobRestService.update_job - Incoming data: {data}") @@ -1136,7 +1118,7 @@ def update_job( schema_version=1, change_id=JobRestService._safe_uuid(delta_payload.change_id), delta_meta=_to_json_safe(meta_payload), - delta_checksum=delta_payload.before_checksum or "", + delta_checksum=delta_payload.before_checksum or None, ) # When status changes via edit, place job at top of new column @@ -1148,7 +1130,7 @@ def update_job( job.save(staff=user, update_fields=["priority", "updated_at"]) result_job = job - # DeltaValidationError subclasses PreconditionFailed, so it must be + # DeltaValidationError subclasses PreconditionFailedError, so it must be # caught first or this arm is unreachable. except DeltaValidationError as exc: JobRestService._record_delta_rejection( @@ -1161,10 +1143,10 @@ def update_job( envelope=delta_payload.to_dict(), change_id=delta_payload.change_id, checksum=delta_payload.before_checksum, - request_etag=delta_payload.etag or if_match, + request_etag=delta_payload.etag or if_match or None, ) raise - except PreconditionFailed: + except PreconditionFailedError: if soft_fail_context: JobRestService._record_delta_rejection(**soft_fail_context) raise @@ -1179,7 +1161,7 @@ def update_job( envelope=delta_payload.to_dict(), change_id=delta_payload.change_id, checksum=delta_payload.before_checksum, - request_etag=delta_payload.etag or if_match, + request_etag=delta_payload.etag or if_match or None, ) raise else: @@ -1194,7 +1176,7 @@ def undo_job_change( job_id: UUID, change_id: UUID, user: Staff, - if_match: str | None = None, + if_match: str, undo_change_id: UUID | None = None, ) -> Job: """Undo a previously recorded delta by reverting to its before state.""" @@ -1243,7 +1225,7 @@ def undo_job_change( change_id=str(change_id), checksum=event.delta_checksum, ) - raise PreconditionFailed( + raise PreconditionFailedError( "Cannot undo change because the current job state no longer matches the original delta" ) @@ -1258,7 +1240,7 @@ def undo_job_change( "before_checksum": checksum, "actor_id": str(user.id), "made_at": timezone.now().isoformat(), - "etag": _current_job_etag_value(job), + "etag": current_job_etag_value(job), "undo_of_change_id": str(change_id), } @@ -1305,7 +1287,9 @@ def toggle_complex_job( @staticmethod @transaction.atomic - def add_job_event(job_id: UUID, description: str, user: Staff) -> Dict[str, Any]: + def add_job_event( + job_id: UUID, description: str, user: Staff, if_match: str + ) -> Dict[str, Any]: """ Adds a manual event to the Job with duplicate prevention. @@ -1324,6 +1308,9 @@ def add_job_event(job_id: UUID, description: str, user: Staff) -> Dict[str, Any] # Lock the job to prevent race conditions job = Job.objects.select_for_update().get(id=job_id) + current_etag = generate_job_etag(job) + if not if_match_satisfied(if_match, current_etag): + raise PreconditionFailedError("ETag mismatch: resource has changed") description_clean = description.strip() @@ -1363,6 +1350,10 @@ def add_job_event(job_id: UUID, description: str, user: Staff) -> Dict[str, Any] f"Event already exists for job {job_id} by user {user.email}. " f"Returning existing event: {event.id}" ) + else: + Job.objects.filter(pk=job.pk).touch_updated_at( + at=timezone.now(), + ) logger.info( f"Event {event.id} {'created' if created else 'found'} " @@ -1384,11 +1375,13 @@ def add_job_event(job_id: UUID, description: str, user: Staff) -> Dict[str, Any] } except Job.DoesNotExist as exc: + persist_app_error(exc, job_id=str(job_id), user_id=str(user.id)) error_msg = f"Job {job_id} not found" logger.error(error_msg) raise ValueError(error_msg) from exc except (ValidationError, IntegrityError) as e: + persist_app_error(e, job_id=str(job_id), user_id=str(user.id)) # Handle duplicate constraint violations logger.warning( f"Duplicate event constraint violation for job {job_id} by user {user.email}: {e}" @@ -1417,34 +1410,27 @@ def add_job_event(job_id: UUID, description: str, user: Staff) -> Dict[str, Any] raise @staticmethod - def delete_job( - job_id: UUID, user: Staff, if_match: str | None = None - ) -> Dict[str, Any]: + def delete_job(job_id: UUID, user: Staff, if_match: str) -> Dict[str, Any]: """ Deletes a Job if allowed by business rules and ETag precondition matches. """ - # Lock row during deletion checks - job = get_object_or_404(Job.objects.select_for_update(), id=job_id) - - # Concurrency check - if if_match: - current_norm = _current_job_etag_value(job) - if current_norm != if_match: - raise PreconditionFailed("ETag mismatch: resource has changed") - - actual_cost_set = job.latest_actual - - if actual_cost_set and ( - actual_cost_set.summary.get("cost", 0) > 0 - or actual_cost_set.summary.get("rev", 0) > 0 - ): - raise ValueError( - "Cannot delete this job because it has real costs or revenue." - ) - - job_name = job.name - job_number = job.job_number with transaction.atomic(): + job = get_object_or_404(Job.objects.select_for_update(), id=job_id) + current_etag = generate_job_etag(job) + if not if_match_satisfied(if_match, current_etag): + raise PreconditionFailedError("ETag mismatch: resource has changed") + + actual_cost_set = job.latest_actual + if actual_cost_set and ( + actual_cost_set.summary.get("cost", 0) > 0 + or actual_cost_set.summary.get("rev", 0) > 0 + ): + raise ValueError( + "Cannot delete this job because it has real costs or revenue." + ) + + job_name = job.name + job_number = job.job_number job.delete() logger.info(f"Job {job_number} '{job_name}' deleted by {user.email}") @@ -1452,9 +1438,7 @@ def delete_job( return {"success": True, "message": f"Job {job_number} deleted successfully"} @staticmethod - def accept_quote( - job_id: UUID, user: Staff, if_match: str | None = None - ) -> Dict[str, Any]: + def accept_quote(job_id: UUID, user: Staff, if_match: str) -> Dict[str, Any]: """ Accept a quote for a job by setting the quote_acceptance_date and changing status to approved. Enforces optimistic concurrency via If-Match (ETag) precondition. @@ -1462,10 +1446,9 @@ def accept_quote( with transaction.atomic(): job = get_object_or_404(Job.objects.select_for_update(), id=job_id) - if if_match: - current_norm = _current_job_etag_value(job) - if current_norm != if_match: - raise PreconditionFailed("ETag mismatch: resource has changed") + current_etag = generate_job_etag(job) + if not if_match_satisfied(if_match, current_etag): + raise PreconditionFailedError("ETag mismatch: resource has changed") if not job.latest_quote: raise ValueError("No quote found for this job") diff --git a/apps/job/services/job_service.py b/apps/job/services/job_service.py index 335ac1bf8..f472cbe5a 100644 --- a/apps/job/services/job_service.py +++ b/apps/job/services/job_service.py @@ -2,12 +2,14 @@ from decimal import Decimal from django.db import transaction -from django.db.models import Sum -from django.db.models.functions import Coalesce from django.utils import timezone -from apps.accounting.enums import InvoiceStatus from apps.accounting.models.invoice import Invoice +from apps.accounting.services.invoice_calculation import ( + INVOICE_VALID_STATUSES, + get_job_invoicing_basis, + get_prior_valid_invoice_total, +) from apps.accounts.models import Staff from apps.job.models import Job from apps.workflow.services.error_persistence import persist_app_error @@ -47,56 +49,39 @@ def archive_complete_jobs(job_ids, staff=None): def get_job_total_value(job: Job) -> Decimal: - """ - Get the total value of a job using the definitive logic: - 1. If invoiced: Use total invoice amount (definitive) - 2. Else if quote job: Use quote revenue - 3. Else (T&M): Use actual revenue - - Args: - job: Job instance + """The total value of a job. - Returns: - Decimal: Total job value + Invoices are definitive once raised; before that the job is worth whatever + its pricing basis says. Both figures come from ``invoice_calculation``, so + this cannot report a value the job could not actually be invoiced for. """ - # Check for invoices first - they override everything - INVOICE_VALID_STATUSES = [ - status - for (status, _) in InvoiceStatus.choices - if status not in ["VOIDED", "DELETED"] - ] - - total_invoiced = Decimal( - Invoice.objects.filter( - job_id=job.id, status__in=INVOICE_VALID_STATUSES - ).aggregate(total=Coalesce(Sum("total_excl_tax"), Decimal("0")))["total"] - ) - + total_invoiced = get_prior_valid_invoice_total(job) if total_invoiced > 0: return total_invoiced - # No invoices - check pricing methodology - if job.pricing_methodology == "fixed_price": - quote = job.get_latest("quote") - if quote and quote.summary: - return Decimal(str(quote.summary.get("rev", 0))) - return Decimal("0.00") - else: - # T&M job - use actual - actual = job.get_latest("actual") - if actual and actual.summary: - return Decimal(str(actual.summary.get("rev", 0))) - return Decimal("0.00") + return get_job_invoicing_basis(job).target_total + + +def update_completion_checklist( + job: Job, updates: dict[str, bool], staff: Staff +) -> Job: + """Tick or untick front-desk checklist items. + + Job.save() turns each changed field into a job-history event through + _FIELD_HANDLERS, so there is no audit code here to keep in step. Validation + of the keys and values belongs to the serializer that accepted them. + """ + for item, value in updates.items(): + setattr(job, item, value) + # Scoped to the ticked items so a concurrent write to an unrelated field — + # fully_invoiced, status, costing — is not overwritten with the value this + # request read before the user touched the checkbox. + job.save(staff=staff, update_fields=list(updates.keys())) + return job def recalculate_job_invoicing_state(job_id: str, staff) -> None: try: - INVOICE_VALID_STATUSES = [ - status - for (status, _) in InvoiceStatus.choices - if status not in ["VOIDED", "DELETED"] - ] - has_invoices = Invoice.objects.filter( job_id=job_id, status__in=INVOICE_VALID_STATUSES ).exists() @@ -111,19 +96,13 @@ def recalculate_job_invoicing_state(job_id: str, staff) -> None: job = Job.objects.select_related("latest_actual", "latest_quote").get(pk=job_id) - total_invoiced = Decimal( - Invoice.objects.filter( - job_id=job_id, status__in=INVOICE_VALID_STATUSES - ).aggregate(total=Coalesce(Sum("total_excl_tax"), Decimal("0")))["total"] + # Compared against the same value the job would be invoiced for, so a + # capped T&M job cannot read "fully invoiced" at one figure while the + # invoice path uses another. + job.fully_invoiced = ( + get_prior_valid_invoice_total(job) + >= get_job_invoicing_basis(job).target_total ) - - # Fixed-price jobs compare against quote; T&M against actuals - if job.pricing_methodology == "fixed_price" and job.latest_quote: - target_amount = Decimal(str(job.latest_quote.total_revenue)) - else: - target_amount = Decimal(str(job.latest_actual.total_revenue)) - - job.fully_invoiced = total_invoiced >= target_amount job.save(staff=staff, update_fields=["fully_invoiced", "updated_at"]) except Job.DoesNotExist: logger.error("Provided job id doesn't exist") @@ -136,7 +115,7 @@ def recalculate_job_invoicing_state(job_id: str, staff) -> None: class JobStaffService: @staticmethod def _touch_job_for_assignment_change(job): - Job.objects.filter(id=job.id).update(updated_at=timezone.now()) + Job.objects.filter(id=job.id).touch_updated_at(at=timezone.now()) @staticmethod def assign_staff_to_job(job_id, staff_id): diff --git a/apps/job/services/kanban_service.py b/apps/job/services/kanban_service.py index 1cd1c1d23..b9d156d02 100644 --- a/apps/job/services/kanban_service.py +++ b/apps/job/services/kanban_service.py @@ -17,6 +17,7 @@ from django.db import transaction from django.db.models import ( Case, + Count, Exists, ExpressionWrapper, F, @@ -35,6 +36,7 @@ from apps.accounting.models import Invoice from apps.accounts.models import Staff from apps.company.models import Company, Person +from apps.job.kanban_version import KanbanDatasetVersion from apps.job.models import CostSet, Job from apps.job.services.kanban_categorization_service import KanbanCategorizationService from apps.workflow.models import CompanyDefaults @@ -62,6 +64,15 @@ class KanbanSerializationContext: people_by_job: Dict[UUID, List[Staff]] +@dataclass(frozen=True) +class KanbanChanges: + """Typed domain result for incremental Kanban reconciliation.""" + + jobs: list[Job] + removed_job_ids: list[UUID] + full_refresh_required: bool + + class KanbanService: """Service class for Kanban business logic.""" @@ -79,6 +90,46 @@ class KanbanService: SEARCH_SCORE_MIN_DISPLAY = 15.0 SEARCH_SCORE_ORDER_NUMBER_MATCH = 75.0 + @staticmethod + def get_kanban_changes(after: str) -> KanbanChanges: + """Return changed cards since an opaque Job dataset version.""" + previous = KanbanDatasetVersion.decode(after) + + current = Job.objects.aggregate( + count=Count("id"), + latest_created=Max("created_at"), + ) + current_topology = KanbanDatasetVersion.from_values( + updated_at=None, + created_at=current["latest_created"], + count=current["count"], + ) + if ( + current_topology.count != previous.count + or current_topology.created_at != previous.created_at + ): + return KanbanChanges( + jobs=[], + removed_job_ids=[], + full_refresh_required=True, + ) + + changed_jobs = Job.objects.filter(updated_at__gt=previous.updated_at) + visible_jobs = KanbanService.filter_kanban_jobs(changed_jobs) + visible_ids = set(visible_jobs.values_list("id", flat=True)) + removed_job_ids = [ + job_id + for job_id in changed_jobs.exclude(id__in=visible_ids).values_list( + "id", flat=True + ) + ] + + return KanbanChanges( + jobs=list(visible_jobs), + removed_job_ids=removed_job_ids, + full_refresh_required=False, + ) + TEXT_SEARCH_FIELDS: List[Dict[str, Any]] = [ { "db_path": "name", @@ -520,12 +571,12 @@ def build_serialization_context(jobs: List[Job]) -> KanbanSerializationContext: CostSet.objects.filter(id__in=costset_ids).values_list("id", "summary") ) - company_ids = {job.company_id for job in jobs} - {None} + company_ids = {job.company_id for job in jobs if job.company_id is not None} company_names = dict( Company.objects.filter(id__in=company_ids).values_list("id", "name") ) - person_ids = {job.person_id for job in jobs} - {None} + person_ids = {job.person_id for job in jobs if job.person_id is not None} person_names = dict( Person.objects.filter(id__in=person_ids).values_list("id", "name") ) @@ -1039,10 +1090,11 @@ def get_jobs_by_kanban_column( # Get valid statuses for this column (simplified approach - column = status) valid_statuses = [column.status_key] # Only the column's main status - jobs_query = Job.objects.filter(status__in=valid_statuses) + jobs_query: QuerySet[Job] = Job.objects.filter(status__in=valid_statuses) jobs_query = KanbanService.filter_kanban_jobs(jobs_query) # Apply search filter if provided + jobs: list[Job] if search_term: ranked_jobs = KanbanService._apply_kanban_search( jobs_query.distinct(), search_term @@ -1062,7 +1114,7 @@ def get_jobs_by_kanban_column( total_count = jobs_query.count() # Apply limit and ordering - jobs = jobs_query.order_by("-priority")[:max_jobs] + jobs = list(jobs_query.order_by("-priority")[:max_jobs]) logger.debug( f"Jobs fetched for column {column_id} (ordered by priority): {[job.job_number for job in jobs]}" ) @@ -1086,7 +1138,7 @@ def get_jobs_by_kanban_column( raise @staticmethod - def filter_kanban_jobs(jobs_query): + def filter_kanban_jobs(jobs_query: QuerySet[Job]) -> QuerySet[Job]: """ Filter jobs for kanban display - excludes 'special' status diff --git a/apps/job/services/workshop_pdf_service.py b/apps/job/services/workshop_pdf_service.py index 594e5ae49..4ae195898 100644 --- a/apps/job/services/workshop_pdf_service.py +++ b/apps/job/services/workshop_pdf_service.py @@ -595,7 +595,7 @@ def _fit_dimensions( return w * scale, h * scale -def convert_html_to_reportlab(html_content): +def convert_html_to_reportlab(html_content: str) -> str: """ Convert Quill HTML to ReportLab-friendly inline markup, with list support. @@ -732,7 +732,9 @@ def create_workshop_pdf(job: Job) -> BytesIO: if not files_to_print: return main_buffer - image_files = [f for f in files_to_print if f.mime_type.startswith("image/")] + image_files = [ + f for f in files_to_print if (f.mime_type or "").startswith("image/") + ] pdf_files = [f for f in files_to_print if f.mime_type == "application/pdf"] return process_attachments(main_buffer, image_files, pdf_files) diff --git a/apps/job/services/workshop_service.py b/apps/job/services/workshop_service.py index 7225641a7..891a5270c 100644 --- a/apps/job/services/workshop_service.py +++ b/apps/job/services/workshop_service.py @@ -130,7 +130,7 @@ def update_entry(self, data) -> CostLine: job_changed = False if "description" in data: - cost_line.desc = data["description"] or "" + cost_line.desc = data["description"] or None has_updates = True if "hours" in data: diff --git a/apps/job/tasks.py b/apps/job/tasks.py index db04e2fb8..6c99afcfe 100644 --- a/apps/job/tasks.py +++ b/apps/job/tasks.py @@ -87,7 +87,7 @@ def create_job_file_thumbnail_task(job_file_id: str) -> None: logger.info("Skipping thumbnail for inactive job file %s.", job_file_id) return - if not job_file.mime_type.startswith("image/"): + if not (job_file.mime_type or "").startswith("image/"): logger.info("Skipping thumbnail for non-image job file %s.", job_file_id) return diff --git a/apps/job/tests/test_chat_api_endpoints.py b/apps/job/tests/test_chat_api_endpoints.py index 6d2ea4be4..591d81b9b 100644 --- a/apps/job/tests/test_chat_api_endpoints.py +++ b/apps/job/tests/test_chat_api_endpoints.py @@ -24,7 +24,7 @@ class ChatAPIEndpointTests(BaseTestCase): """Test chat API endpoints""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.client_api = APIClient() @@ -76,7 +76,7 @@ def setUp(self): # Authenticate for API requests self.client_api.force_authenticate(user=self.staff) - def test_chat_history_get_empty(self): + def test_chat_history_get_empty(self) -> None: """Test getting empty chat history""" response = self.client_api.get(self.chat_history_url) @@ -84,7 +84,7 @@ def test_chat_history_get_empty(self): self.assertTrue(response.data["success"]) self.assertEqual(response.data["data"]["messages"], []) - def test_chat_history_get_with_messages(self): + def test_chat_history_get_with_messages(self) -> None: """Test getting chat history with existing messages""" JobQuoteChat.objects.create( job=self.job, @@ -112,7 +112,7 @@ def test_chat_history_get_with_messages(self): self.assertEqual(messages[1]["content"], "Hi there!") self.assertEqual(messages[1]["role"], "assistant") - def test_chat_history_post_create_message(self): + def test_chat_history_post_create_message(self) -> None: """Test creating a new chat message""" data = { "message_id": "test-create-msg", @@ -135,7 +135,7 @@ def test_chat_history_post_create_message(self): self.assertEqual(message.content, "Test message creation") self.assertEqual(message.role, "user") - def test_chat_history_post_invalid_data(self): + def test_chat_history_post_invalid_data(self) -> None: """Test creating message with invalid data""" data = { "message_id": "test-invalid-data", @@ -151,7 +151,7 @@ def test_chat_history_post_invalid_data(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_chat_history_delete_message(self): + def test_chat_history_delete_message(self) -> None: """Test deleting a single chat message""" message = JobQuoteChat.objects.create( job=self.job, @@ -172,7 +172,7 @@ def test_chat_history_delete_message(self): # Verify message was deleted self.assertFalse(JobQuoteChat.objects.filter(id=message.id).exists()) - def test_chat_history_delete_all(self): + def test_chat_history_delete_all(self) -> None: """Test deleting all chat messages for a job""" # Create multiple messages JobQuoteChat.objects.create( @@ -207,7 +207,7 @@ def test_chat_history_delete_all(self): # Verify all messages were deleted self.assertEqual(JobQuoteChat.objects.filter(job=self.job).count(), 0) - def test_chat_history_job_not_found(self): + def test_chat_history_job_not_found(self) -> None: """Test accessing chat history for non-existent job""" non_existent_job_id = str(uuid.uuid4()) url = reverse( @@ -253,7 +253,7 @@ def test_chat_interaction_success(self, mock_generate_response): job_id=self.job.id, user_message="Test user message for AI", mode=None ) - def test_chat_interaction_missing_message(self): + def test_chat_interaction_missing_message(self) -> None: """Test chat interaction with missing message""" data = {} @@ -269,7 +269,7 @@ def test_chat_interaction_missing_message(self): "error", response.data ) # Error message returned for invalid input - def test_chat_interaction_empty_message(self): + def test_chat_interaction_empty_message(self) -> None: """Test chat interaction with empty message""" data = { "message": "", @@ -284,7 +284,7 @@ def test_chat_interaction_empty_message(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertFalse(response.data["success"]) - def test_chat_interaction_job_not_found(self): + def test_chat_interaction_job_not_found(self) -> None: """Test chat interaction with non-existent job""" non_existent_job_id = str(uuid.uuid4()) url = reverse( @@ -345,7 +345,7 @@ def test_chat_interaction_internal_error(self, mock_generate_response): class ChatAPIPermissionTests(BaseTestCase): """Test API permissions and authentication""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.client_api = APIClient() @@ -396,7 +396,7 @@ def setUp(self): "jobs:job_quote_chat_interaction", kwargs={"job_id": str(self.job.id)} ) - def test_unauthenticated_access(self): + def test_unauthenticated_access(self) -> None: """Test unauthenticated access to chat endpoints""" # Test chat history endpoint response = self.client_api.get(self.chat_history_url) @@ -410,7 +410,7 @@ def test_unauthenticated_access(self): ) self.assertIn(response.status_code, [200, 401, 403]) - def test_authenticated_access(self): + def test_authenticated_access(self) -> None: """Test authenticated access to chat endpoints""" self.client_api.force_authenticate(user=self.staff) @@ -418,7 +418,7 @@ def test_authenticated_access(self): response = self.client_api.get(self.chat_history_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_admin_access(self): + def test_admin_access(self) -> None: """Test admin access to chat endpoints""" self.client_api.force_authenticate(user=self.admin_staff) @@ -430,7 +430,7 @@ def test_admin_access(self): class ChatAPIValidationTests(BaseTestCase): """Test API data validation""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.client_api = APIClient() @@ -469,7 +469,7 @@ def setUp(self): ) self.client_api.force_authenticate(user=self.staff) - def test_create_message_valid_roles(self): + def test_create_message_valid_roles(self) -> None: """Test creating messages with valid roles""" valid_roles = ["user", "assistant"] @@ -489,7 +489,7 @@ def test_create_message_valid_roles(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data["data"]["role"], role) - def test_create_message_invalid_role(self): + def test_create_message_invalid_role(self) -> None: """Test creating message with invalid role""" data = { "message_id": "test-invalid-role", @@ -505,7 +505,7 @@ def test_create_message_invalid_role(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_create_message_missing_content(self): + def test_create_message_missing_content(self) -> None: """Test creating message with missing content""" data = { "message_id": "test-missing-content", @@ -520,7 +520,7 @@ def test_create_message_missing_content(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_create_message_empty_content(self): + def test_create_message_empty_content(self) -> None: """Test creating message with empty content""" data = { "message_id": "test-empty-content", @@ -536,7 +536,7 @@ def test_create_message_empty_content(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_create_message_with_metadata(self): + def test_create_message_with_metadata(self) -> None: """Test creating message with metadata""" data = { "message_id": "test-with-metadata", @@ -561,7 +561,7 @@ def test_create_message_with_metadata(self): response.data["data"]["metadata"]["custom_field"], "test_value" ) - def test_nonexistent_job_id(self): + def test_nonexistent_job_id(self) -> None: """Test API with non-existent job ID""" # Use a valid UUID format that doesn't exist in the database nonexistent_url = reverse( diff --git a/apps/job/tests/test_chat_performance.py b/apps/job/tests/test_chat_performance.py index c33313137..c74875bc0 100644 --- a/apps/job/tests/test_chat_performance.py +++ b/apps/job/tests/test_chat_performance.py @@ -44,7 +44,7 @@ def create_text_response(content: str) -> Mock: class ChatQueryOptimizationTests(BaseTestCase): """Test chat database query counts and bulk operations""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -76,7 +76,7 @@ def setUp(self): self.service = ChatService() - def test_database_query_optimization(self): + def test_database_query_optimization(self) -> None: """Test database query optimization""" # Create conversation history for i in range(50): @@ -110,7 +110,7 @@ def test_database_query_optimization(self): f"Expected <=7 queries, got {len(ctx.captured_queries)}", ) - def test_bulk_message_creation(self): + def test_bulk_message_creation(self) -> None: """Test bulk database message creation""" jobs = [] for i in range(3): diff --git a/apps/job/tests/test_chat_service.py b/apps/job/tests/test_chat_service.py index dfddf2288..264fcd895 100644 --- a/apps/job/tests/test_chat_service.py +++ b/apps/job/tests/test_chat_service.py @@ -118,7 +118,7 @@ def create_mock_llm(model_name: str = "test-model") -> Mock: class ChatServiceConfigurationTests(BaseTestCase): """Test service configuration and initialization""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -143,14 +143,14 @@ def setUp(self): self.service = ChatService() - def test_service_initialization(self): + def test_service_initialization(self) -> None: """Test service initializes with required components""" self.assertIsNotNone(self.service.quoting_tool) self.assertIsNotNone(self.service.query_tool) self.assertIsNotNone(self.service.mode_controller) self.assertIsNotNone(self.service.file_service) - def test_llm_service_no_provider(self): + def test_llm_service_no_provider(self) -> None: """Test LLM service creation fails when no AI provider configured""" # Remove all AI providers AIProvider.objects.all().delete() @@ -160,7 +160,7 @@ def test_llm_service_no_provider(self): self.assertIn("No AI provider configured", str(context.exception)) - def test_llm_service_no_api_key(self): + def test_llm_service_no_api_key(self) -> None: """Test LLM service creation fails when provider has no API key""" AIProvider.objects.all().delete() AIProvider.objects.create( @@ -175,7 +175,7 @@ def test_llm_service_no_api_key(self): self.assertIn("missing an API key", str(context.exception)) - def test_llm_service_no_model_name(self): + def test_llm_service_no_model_name(self) -> None: """Test LLM service creation fails when provider has no model name""" AIProvider.objects.all().delete() AIProvider.objects.create( @@ -190,7 +190,7 @@ def test_llm_service_no_model_name(self): self.assertIn("missing a model name", str(context.exception)) - def test_system_prompt_generation(self): + def test_system_prompt_generation(self) -> None: """Test system prompt includes job context""" prompt = self.service._get_system_prompt(self.job) @@ -200,7 +200,7 @@ def test_system_prompt_generation(self): self.assertIn(self.company.name, prompt) self.assertIn(self.job.description, prompt) - def test_mcp_tools_definition(self): + def test_mcp_tools_definition(self) -> None: """Test MCP tools are properly defined in OpenAI format""" tools = self.service._get_mcp_tools() @@ -228,7 +228,7 @@ def test_mcp_tools_definition(self): for expected_tool in expected_tools: self.assertIn(expected_tool, tool_names) - def test_role_conversion(self): + def test_role_conversion(self) -> None: """Test database role to OpenAI role conversion""" # Both 'user' and 'assistant' should map to themselves self.assertEqual(ChatService._to_openai_role("user"), "user") @@ -238,18 +238,18 @@ def test_role_conversion(self): class ChatServiceToolExecutionTests(TestCase): """Test MCP tool execution""" - def setUp(self): + def setUp(self) -> None: self.service = ChatService() # Create a mock for the quoting_tool instance attribute self.mock_quoting_tool = Mock() self.service.quoting_tool = self.mock_quoting_tool - def test_execute_unknown_tool(self): + def test_execute_unknown_tool(self) -> None: """Test execution of unknown tool""" result = self.service._execute_mcp_tool("unknown_tool", {}) self.assertEqual(result, "Unknown tool: unknown_tool") - def test_execute_tool_with_exception(self): + def test_execute_tool_with_exception(self) -> None: """Test tool execution with exception""" self.mock_quoting_tool.search_products.side_effect = Exception("Tool error") @@ -262,7 +262,7 @@ def test_execute_tool_with_exception(self): class ChatServiceResponseGenerationTests(BaseTestCase): """Test AI response generation with database transactions""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -294,7 +294,7 @@ def setUp(self): self.service = ChatService() - def test_job_not_found(self): + def test_job_not_found(self) -> None: """Test response generation with non-existent job""" non_existent_job_id = str(uuid.uuid4()) @@ -412,7 +412,7 @@ def test_generate_response_error_handling(self, mock_get_llm): class ChatServiceMultimodalTests(BaseTestCase): """Test multimodal content handling""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -436,7 +436,7 @@ def setUp(self): self.service = ChatService() - def test_build_multimodal_content_no_files(self): + def test_build_multimodal_content_no_files(self) -> None: """Test _build_multimodal_content returns text when no files""" mock_llm = MockLLMResponseBuilder.create_mock_llm() mock_llm.supports_vision.return_value = True @@ -445,7 +445,7 @@ def test_build_multimodal_content_no_files(self): self.assertEqual(result, "Test message") - def test_build_multimodal_content_no_vision_support(self): + def test_build_multimodal_content_no_vision_support(self) -> None: """Test _build_multimodal_content falls back to text reference""" mock_llm = MockLLMResponseBuilder.create_mock_llm() mock_llm.supports_vision.return_value = False @@ -461,7 +461,7 @@ def test_build_multimodal_content_no_vision_support(self): self.assertIn("Test message", result) self.assertIn("[Attached files: test.png]", result) - def test_build_multimodal_content_file_not_found(self): + def test_build_multimodal_content_file_not_found(self) -> None: """Test _build_multimodal_content handles missing files""" mock_llm = MockLLMResponseBuilder.create_mock_llm() mock_llm.supports_vision.return_value = True @@ -482,7 +482,7 @@ def test_build_multimodal_content_file_not_found(self): text_content = " ".join(p["text"] for p in text_parts) self.assertIn("not found", text_content) - def test_build_multimodal_content_with_image(self): + def test_build_multimodal_content_with_image(self) -> None: """Test _build_multimodal_content with image file""" mock_llm = MockLLMResponseBuilder.create_mock_llm() mock_llm.supports_vision.return_value = True @@ -584,7 +584,7 @@ def test_build_multimodal_content_with_image(self): finally: os.unlink(temp_path) - def test_build_multimodal_content_unsupported_file_type(self): + def test_build_multimodal_content_unsupported_file_type(self) -> None: """Test _build_multimodal_content with unsupported file type""" mock_llm = MockLLMResponseBuilder.create_mock_llm() mock_llm.supports_vision.return_value = True @@ -611,7 +611,7 @@ def test_build_multimodal_content_unsupported_file_type(self): class ChatServiceIntegrationTests(BaseTestCase): """Integration tests for the complete chat flow""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -669,7 +669,7 @@ def test_complete_chat_flow(self, mock_get_llm): self.assertIn("user_message", saved_message.metadata) self.assertIn("tool_definitions", saved_message.metadata) - def test_conversation_persistence(self): + def test_conversation_persistence(self) -> None: """Test that conversation history is properly maintained""" # Create a sequence of messages messages = [ @@ -699,7 +699,7 @@ def test_conversation_persistence(self): class ChatServiceModeResponseTests(BaseTestCase): """Test mode-based response generation""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() diff --git a/apps/job/tests/test_completion_checklist.py b/apps/job/tests/test_completion_checklist.py new file mode 100644 index 000000000..b13240e7d --- /dev/null +++ b/apps/job/tests/test_completion_checklist.py @@ -0,0 +1,197 @@ +"""Tests for the front-desk completion checklist. + +The items are Job fields, so the audit trail comes from the job's own +field-change machinery. These tests pin that each tick is attributed and that +ticking nothing still lets a job be invoiced — the checklist records, it does +not gate. +""" + +import uuid +from datetime import date +from decimal import Decimal + +from django.urls import reverse +from django.utils import timezone +from rest_framework.response import Response + +from apps.accounting.services.invoice_calculation import calculate_invoice_amount +from apps.accounts.models import Staff +from apps.company.models import Company +from apps.job.models import Job, JobEvent +from apps.job.models.costing import CostLine +from apps.testing import BaseAPITestCase + +CHECKLIST_EVENT = "completion_checklist_updated" + + +class TestCompletionChecklist(BaseAPITestCase): + def setUp(self) -> None: + self.client_obj = Company.objects.create( + name="Test Company", + xero_last_modified=timezone.now(), + ) + self.job = Job( + company=self.client_obj, + name="Test Job", + pricing_methodology="time_materials", + ) + self.job.save(staff=self.test_staff) + self.url = reverse("jobs:job_finish_rest", args=[self.job.id]) + # Ticking an item is an office action; the shared test_staff is + # deliberately neither office nor workshop. + self.office_staff = Staff.objects.create_user( + email="office@example.com", + password="testpass", + first_name="Office", + last_name="Person", + is_office_staff=True, + ) + self.client.force_authenticate(user=self.office_staff) + + def _patch(self, payload: dict[str, object]) -> Response: + return self.client.patch(self.url, data=payload, format="json") + + # --- Reading --- + + def test_a_new_job_has_nothing_ticked(self) -> None: + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 200) + for field in Job.COMPLETION_CHECKLIST_FIELDS: + self.assertFalse(response.data["checklist"][field], msg=field) + + def test_every_item_is_exposed(self) -> None: + """The API shape and the field tuple must not drift apart.""" + response = self.client.get(self.url) + + self.assertEqual( + set(response.data["checklist"].keys()), set(Job.COMPLETION_CHECKLIST_FIELDS) + ) + + # --- Updating --- + + def test_a_missing_job_is_not_found(self) -> None: + """A bad job id is a 404, not the 500 the old handler produced.""" + missing = reverse("jobs:job_finish_rest", args=[uuid.uuid4()]) + + self.assertEqual(self.client.get(missing).status_code, 404) + self.assertEqual( + self.client.patch( + missing, data={"released": True}, format="json" + ).status_code, + 404, + ) + + def test_ticking_one_item_leaves_the_others_alone(self) -> None: + response = self._patch({"materials_checked": True}) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.data["checklist"]["materials_checked"]) + self.assertFalse(response.data["checklist"]["foreman_signed_off"]) + self.assertFalse(response.data["checklist"]["released"]) + + def test_every_item_can_be_ticked(self) -> None: + for field in Job.COMPLETION_CHECKLIST_FIELDS: + with self.subTest(field=field): + response = self._patch({field: True}) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.data["checklist"][field]) + + def test_a_tick_can_be_withdrawn(self) -> None: + self._patch({"released": True}) + + response = self._patch({"released": False}) + + self.assertFalse(response.data["checklist"]["released"]) + + def test_unknown_item_is_rejected(self) -> None: + response = self._patch({"everything_is_fine": True}) + + self.assertEqual(response.status_code, 400) + + def test_a_value_that_is_not_a_boolean_is_rejected(self) -> None: + response = self._patch({"materials_checked": "maybe"}) + + self.assertEqual(response.status_code, 400) + + def test_a_rejected_payload_applies_none_of_it(self) -> None: + """An unknown key fails the whole payload rather than half-applying it.""" + response = self._patch({"materials_checked": True, "nonsense": True}) + + self.assertEqual(response.status_code, 400) + self.job.refresh_from_db() + self.assertFalse(self.job.materials_checked) + + # --- Audit --- + + def test_each_changed_item_is_attributed_in_job_history(self) -> None: + self._patch({"foreman_signed_off": True}) + + event = JobEvent.objects.filter( + job=self.job, event_type=CHECKLIST_EVENT + ).latest("timestamp") + self.assertEqual(event.staff, self.office_staff) + self.assertEqual(event.description, "Foreman signed the job off") + + def test_withdrawing_a_tick_is_audited(self) -> None: + self._patch({"released": True}) + self._patch({"released": False}) + + events = JobEvent.objects.filter( + job=self.job, event_type=CHECKLIST_EVENT + ).order_by("timestamp") + self.assertEqual( + [e.description for e in events], ["Job released", "Job release withdrawn"] + ) + + def test_reticking_the_same_value_adds_no_event(self) -> None: + self._patch({"materials_checked": True}) + self._patch({"materials_checked": True}) + + self.assertEqual( + JobEvent.objects.filter(job=self.job, event_type=CHECKLIST_EVENT).count(), 1 + ) + + # --- The checklist records, it does not gate --- + + def test_ticking_everything_does_not_change_job_status(self) -> None: + original_status = self.job.status + + for field in Job.COMPLETION_CHECKLIST_FIELDS: + self._patch({field: True}) + + self.job.refresh_from_db() + self.assertEqual(self.job.status, original_status) + + def test_an_untouched_checklist_does_not_block_invoicing(self) -> None: + self._add_actual_revenue(Decimal("500")) + + result = calculate_invoice_amount(self.job, mode="invoice_costs_to_date") + + self.assertEqual(result.calculated_amount, Decimal("500")) + + def test_ticks_do_not_change_the_invoice_amount(self) -> None: + self._add_actual_revenue(Decimal("500")) + before = calculate_invoice_amount( + self.job, mode="invoice_costs_to_date" + ).calculated_amount + + self._patch({"timesheets_collected": True}) + self._patch({"materials_checked": True}) + + self.job.refresh_from_db() + after = calculate_invoice_amount( + self.job, mode="invoice_costs_to_date" + ).calculated_amount + self.assertEqual(before, after) + + def _add_actual_revenue(self, revenue: Decimal) -> None: + CostLine.objects.create( + cost_set=self.job.latest_actual, + kind="adjust", + desc="Test line", + quantity=Decimal("1.000"), + unit_cost=Decimal("0.00"), + unit_rev=revenue, + accounting_date=date.today(), + ) diff --git a/apps/job/tests/test_delivery_docket_service.py b/apps/job/tests/test_delivery_docket_service.py index f56bd62d7..2c16a2f8d 100644 --- a/apps/job/tests/test_delivery_docket_service.py +++ b/apps/job/tests/test_delivery_docket_service.py @@ -22,7 +22,7 @@ class GenerateDeliveryDocketTests(BaseTestCase): """``generate_delivery_docket`` must attribute its JobEvent to a staff.""" - def setUp(self): + def setUp(self) -> None: super().setUp() # Sandbox the on-disk write so tests don't touch the real Dropbox path. @@ -43,12 +43,12 @@ def setUp(self): staff=self.test_staff, ) - def tearDown(self): + def tearDown(self) -> None: self._settings_override.disable() shutil.rmtree(self._tmp_dropbox, ignore_errors=True) super().tearDown() - def test_generate_attributes_jobevent_to_calling_staff(self): + def test_generate_attributes_jobevent_to_calling_staff(self) -> None: """The emitted JobEvent must carry the staff who triggered the print.""" pdf_buffer, job_file = generate_delivery_docket(self.job, staff=self.test_staff) diff --git a/apps/job/tests/test_delta_checksum.py b/apps/job/tests/test_delta_checksum.py index 6cc9e3a54..e881c7dfd 100644 --- a/apps/job/tests/test_delta_checksum.py +++ b/apps/job/tests/test_delta_checksum.py @@ -7,7 +7,7 @@ from apps.job.services.delta_checksum import compute_job_delta_checksum -def test_checksum_is_deterministic_with_sorted_fields(): +def test_checksum_is_deterministic_with_sorted_fields() -> None: """Field-order differences must not cause false delta conflicts. This catches checksum code that preserves dict insertion order, because the @@ -22,7 +22,7 @@ def test_checksum_is_deterministic_with_sorted_fields(): assert checksum_a == checksum_b -def test_checksum_trims_strings_and_normalises_null(): +def test_checksum_trims_strings_and_normalises_null() -> None: """Equivalent form values must hash the same after canonicalisation. This catches regressions where harmless input padding creates a false @@ -37,7 +37,7 @@ def test_checksum_trims_strings_and_normalises_null(): ) -def test_checksum_handles_decimal_and_boolean_and_numbers(): +def test_checksum_handles_decimal_and_boolean_and_numbers() -> None: """Numeric representation differences must not break optimistic locking. This catches checksum changes that treat ``5.10`` and ``5.100`` as @@ -57,7 +57,7 @@ def test_checksum_handles_decimal_and_boolean_and_numbers(): ) -def test_checksum_handles_datetimes_and_dates(): +def test_checksum_handles_datetimes_and_dates() -> None: """Timezone representation differences must not create false conflicts. This catches UTC-aware and naive-UTC datetimes hashing differently when @@ -75,7 +75,7 @@ def test_checksum_handles_datetimes_and_dates(): ) -def test_checksum_respects_explicit_field_subset(): +def test_checksum_respects_explicit_field_subset() -> None: """Partial deltas must lock only the fields they are changing. This catches checksum code that ignores the requested field subset and @@ -94,7 +94,7 @@ def test_checksum_respects_explicit_field_subset(): assert checksum_all != checksum_subset -def test_checksum_raises_when_job_id_missing(): +def test_checksum_raises_when_job_id_missing() -> None: """A delta without a job identity must not produce a reusable checksum. This catches callers accidentally hashing orphaned field values and then @@ -104,7 +104,7 @@ def test_checksum_raises_when_job_id_missing(): compute_job_delta_checksum("", {"name": "Part A"}) -def test_checksum_raises_for_missing_field_in_subset(): +def test_checksum_raises_for_missing_field_in_subset() -> None: """Requested fields missing from the snapshot must fail before mutation. This catches envelope-building bugs where the checksum omits a field the diff --git a/apps/job/tests/test_event_deduplication.py b/apps/job/tests/test_event_deduplication.py index 52177ea0d..b47865630 100644 --- a/apps/job/tests/test_event_deduplication.py +++ b/apps/job/tests/test_event_deduplication.py @@ -4,6 +4,7 @@ from apps.accounts.models import Staff from apps.company.models import Company +from apps.job.etag import generate_job_etag from apps.job.models import Job, JobEvent from apps.job.services.job_rest_service import JobRestService from apps.testing import BaseTestCase @@ -18,7 +19,7 @@ class EventDeduplicationTest(BaseTestCase): These tests catch both duplicate leakage and over-broad deduplication. """ - def setUp(self): + def setUp(self) -> None: self.user = Staff.objects.create_user( email="test@example.com", password="testpass123", @@ -40,7 +41,7 @@ def setUp(self): staff=self.test_staff, ) - def test_model_prevents_duplicate_manual_events(self): + def test_model_prevents_duplicate_manual_events(self) -> None: """Direct model writes must not bypass manual-note duplicate guards. This catches imports/admin paths that create ``JobEvent`` rows without @@ -62,7 +63,7 @@ def test_model_prevents_duplicate_manual_events(self): event_type="manual_note", ) - def test_create_safe_method_prevents_duplicates(self): + def test_create_safe_method_prevents_duplicates(self) -> None: """Retry-safe creation must return the existing note, not error or duplicate. This catches callers that use ``create_safe`` during retry flows and @@ -85,7 +86,7 @@ def test_create_safe_method_prevents_duplicates(self): self.assertFalse(created2) self.assertEqual(event1.id, event2.id) - def test_service_prevents_duplicate_events(self): + def test_service_prevents_duplicate_events(self) -> None: """The REST service must collapse immediate duplicate submissions. This catches frontend retry/double-click regressions by going through @@ -94,11 +95,22 @@ def test_service_prevents_duplicate_events(self): """ description = "Test service event" - result1 = JobRestService.add_job_event(self.job.id, description, self.user) + result1 = JobRestService.add_job_event( + self.job.id, + description, + self.user, + generate_job_etag(self.job), + ) self.assertTrue(result1["success"]) self.assertFalse(result1.get("duplicate_prevented", False)) - result2 = JobRestService.add_job_event(self.job.id, description, self.user) + self.job.refresh_from_db() + result2 = JobRestService.add_job_event( + self.job.id, + description, + self.user, + generate_job_etag(self.job), + ) self.assertTrue(result2["success"]) self.assertTrue(result2.get("duplicate_prevented", False)) @@ -110,7 +122,7 @@ def test_service_prevents_duplicate_events(self): ) self.assertEqual(events.count(), 1) - def test_different_users_can_create_same_event(self): + def test_different_users_can_create_same_event(self) -> None: """Deduplication must not erase another staff member's identical note. This catches over-broad dedup identity that keys only on job and note @@ -126,10 +138,21 @@ def test_different_users_can_create_same_event(self): description = "Same description" - result1 = JobRestService.add_job_event(self.job.id, description, self.user) + result1 = JobRestService.add_job_event( + self.job.id, + description, + self.user, + generate_job_etag(self.job), + ) self.assertTrue(result1["success"]) - result2 = JobRestService.add_job_event(self.job.id, description, user2) + self.job.refresh_from_db() + result2 = JobRestService.add_job_event( + self.job.id, + description, + user2, + generate_job_etag(self.job), + ) self.assertTrue(result2["success"]) self.assertFalse(result2.get("duplicate_prevented", False)) @@ -138,7 +161,7 @@ def test_different_users_can_create_same_event(self): ) self.assertEqual(events.count(), 2) - def test_automatic_events_not_affected(self): + def test_automatic_events_not_affected(self) -> None: """Automatic audit events must not be collapsed like manual notes. This catches a dedup refactor that applies manual-note rules to status @@ -164,7 +187,9 @@ def test_automatic_events_not_affected(self): events = JobEvent.objects.filter(job=self.job, event_type="status_changed") self.assertEqual(events.count(), 3) - def test_manual_note_dedup_identity_normalises_text_only_within_same_user(self): + def test_manual_note_dedup_identity_normalises_text_only_within_same_user( + self, + ) -> None: """Manual note identity should ignore text case/spacing but keep staff. This catches two plausible mistakes: failing to collapse harmless text diff --git a/apps/job/tests/test_job_event_tracking.py b/apps/job/tests/test_job_event_tracking.py index c632d7320..d5591eabf 100644 --- a/apps/job/tests/test_job_event_tracking.py +++ b/apps/job/tests/test_job_event_tracking.py @@ -1,6 +1,7 @@ """Tests for automatic JobEvent tracking via Job.save().""" from datetime import timedelta +from typing import Any from django.contrib.auth import get_user_model from django.test import SimpleTestCase @@ -25,7 +26,7 @@ class JobEventTrackingTest(BaseTestCase): """Verify that Job.save() creates events for tracked field changes.""" - def setUp(self): + def setUp(self) -> None: self.user = Staff.objects.create_user( email="tracker@example.com", password="testpass123", @@ -46,7 +47,7 @@ def setUp(self): ) self.job.save(staff=self.user) - def test_status_change_creates_event(self): + def test_status_change_creates_event(self) -> None: self.job.status = "in_progress" self.job.save(staff=self.user) @@ -58,7 +59,7 @@ def test_status_change_creates_event(self): self.assertEqual(event.delta_before["status"], "draft") self.assertEqual(event.delta_after["status"], "in_progress") - def test_event_list_serializes_staff(self): + def test_event_list_serializes_staff(self) -> None: JobEvent.objects.create( job=self.job, staff=self.user, @@ -73,7 +74,7 @@ def test_event_list_serializes_staff(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.data["events"][0]["staff"], "Test Tracker") - def test_status_change_to_archived_clears_assigned_staff(self): + def test_status_change_to_archived_clears_assigned_staff(self) -> None: assigned_staff = Staff.objects.create_user( email="assigned@example.com", password="testpass123", @@ -87,7 +88,7 @@ def test_status_change_to_archived_clears_assigned_staff(self): self.assertFalse(self.job.people.exists()) - def test_name_change_creates_event(self): + def test_name_change_creates_event(self) -> None: self.job.name = "Renamed Job" self.job.save(staff=self.user) @@ -100,13 +101,13 @@ def test_name_change_creates_event(self): self.assertIn("name", event.delta_before) self.assertEqual(event.delta_after["name"], "Renamed Job") - def test_no_change_creates_no_event(self): + def test_no_change_creates_no_event(self) -> None: count_before = JobEvent.objects.filter(job=self.job).count() self.job.save(staff=self.user) count_after = JobEvent.objects.filter(job=self.job).count() self.assertEqual(count_before, count_after) - def test_multiple_field_changes_create_single_event(self): + def test_multiple_field_changes_create_single_event(self) -> None: count_before = JobEvent.objects.filter(job=self.job).count() self.job.name = "New Name" self.job.order_number = "PO-999" @@ -118,14 +119,14 @@ def test_multiple_field_changes_create_single_event(self): self.assertIn("name", event.delta_before) self.assertIn("order_number", event.delta_before) - def test_untracked_field_change_creates_no_event(self): + def test_untracked_field_change_creates_no_event(self) -> None: count_before = JobEvent.objects.filter(job=self.job).count() self.job.fully_invoiced = True self.job.save(staff=self.user) count_after = JobEvent.objects.filter(job=self.job).count() self.assertEqual(count_before, count_after) - def test_enrichment_kwargs_passed_to_event(self): + def test_enrichment_kwargs_passed_to_event(self) -> None: import uuid cid = uuid.uuid4() @@ -144,14 +145,14 @@ def test_enrichment_kwargs_passed_to_event(self): self.assertEqual(event.delta_meta, {"fields": ["name"]}) self.assertEqual(event.delta_checksum, "abc123") - def test_event_type_override(self): + def test_event_type_override(self) -> None: self.job.status = "approved" self.job.save(staff=self.user, event_type_override="quote_accepted") event = JobEvent.objects.filter(job=self.job).order_by("-timestamp").first() self.assertEqual(event.event_type, "quote_accepted") - def test_status_transition_to_completed_persists_completed_at(self): + def test_status_transition_to_completed_persists_completed_at(self) -> None: self.assertIsNone(self.job.completed_at) self.job.status = "recently_completed" @@ -161,7 +162,7 @@ def test_status_transition_to_completed_persists_completed_at(self): self.assertEqual(self.job.status, "recently_completed") self.assertIsNotNone(self.job.completed_at) - def test_status_revert_clears_completed_at(self): + def test_status_revert_clears_completed_at(self) -> None: self.job.status = "recently_completed" self.job.save(staff=self.user) self.job.refresh_from_db() @@ -173,7 +174,7 @@ def test_status_revert_clears_completed_at(self): self.job.refresh_from_db() self.assertIsNone(self.job.completed_at) - def test_accepted_for_work_at_returns_first_approved_event(self): + def test_accepted_for_work_at_returns_first_approved_event(self) -> None: first = timezone.now() - timedelta(days=3) second = timezone.now() - timedelta(days=1) JobEvent.objects.create( @@ -193,7 +194,9 @@ def test_accepted_for_work_at_returns_first_approved_event(self): self.assertEqual(self.job.accepted_for_work_at, first) - def test_accepted_for_work_at_uses_in_progress_if_approved_was_skipped(self): + def test_accepted_for_work_at_uses_in_progress_if_approved_was_skipped( + self, + ) -> None: accepted = timezone.now() - timedelta(days=2) JobEvent.objects.create( job=self.job, @@ -205,7 +208,7 @@ def test_accepted_for_work_at_uses_in_progress_if_approved_was_skipped(self): self.assertEqual(self.job.accepted_for_work_at, accepted) - def test_accepted_for_work_at_prefers_approved_over_later_in_progress(self): + def test_accepted_for_work_at_prefers_approved_over_later_in_progress(self) -> None: approved = timezone.now() - timedelta(days=4) in_progress = timezone.now() - timedelta(days=1) JobEvent.objects.create( @@ -227,12 +230,14 @@ def test_accepted_for_work_at_prefers_approved_over_later_in_progress(self): def test_accepted_for_work_at_ignores_quote_acceptance_date_without_status_event( self, - ): + ) -> None: self.job.quote_acceptance_date = timezone.now() - timedelta(days=5) self.assertIsNone(self.job.accepted_for_work_at) - def test_accepted_for_work_at_uses_approved_status_from_quote_accepted_event(self): + def test_accepted_for_work_at_uses_approved_status_from_quote_accepted_event( + self, + ) -> None: accepted = timezone.now() - timedelta(days=3) JobEvent.objects.create( job=self.job, @@ -244,7 +249,7 @@ def test_accepted_for_work_at_uses_approved_status_from_quote_accepted_event(sel self.assertEqual(self.job.accepted_for_work_at, accepted) - def test_status_change_with_update_fields_persists_completed_at(self): + def test_status_change_with_update_fields_persists_completed_at(self) -> None: self.assertIsNone(self.job.completed_at) self.job.status = "recently_completed" @@ -258,25 +263,25 @@ def test_status_change_with_update_fields_persists_completed_at(self): class JobEventDescriptionTest(SimpleTestCase): """Pure-Python tests for the computed description property and builders.""" - def _event(self, event_type, detail): + def _event(self, event_type: str, detail: dict[str, Any]) -> JobEvent: return JobEvent(event_type=event_type, detail=detail) - def test_description_property_delegates_to_build_description(self): + def test_description_property_delegates_to_build_description(self) -> None: event = self._event("manual_note", {"note_text": "A scribbled note"}) self.assertEqual(event.description, "A scribbled note") self.assertEqual(event.description, event.build_description()) - def test_legacy_description_takes_precedence(self): + def test_legacy_description_takes_precedence(self) -> None: event = self._event( "job_updated", {"legacy_description": "Old free-text", "changes": []} ) self.assertEqual(event.description, "Old free-text") - def test_unknown_event_type_returns_sentinel(self): + def test_unknown_event_type_returns_sentinel(self) -> None: event = self._event("never_seen_event_type", {}) self.assertEqual(event.description, "(never_seen_event_type)") - def test_friendly_boolean_rejected_set(self): + def test_friendly_boolean_rejected_set(self) -> None: event = self._event( "job_rejected", { @@ -287,7 +292,7 @@ def test_friendly_boolean_rejected_set(self): ) self.assertEqual(event.description, "Job marked as rejected") - def test_friendly_boolean_rejected_cleared(self): + def test_friendly_boolean_rejected_cleared(self) -> None: event = self._event( "job_updated", { @@ -298,7 +303,7 @@ def test_friendly_boolean_rejected_cleared(self): ) self.assertEqual(event.description, "Rejection cleared") - def test_friendly_boolean_complex_job(self): + def test_friendly_boolean_complex_job(self) -> None: event = self._event( "job_updated", { @@ -309,7 +314,7 @@ def test_friendly_boolean_complex_job(self): ) self.assertEqual(event.description, "Marked as complex job") - def test_friendly_boolean_paid(self): + def test_friendly_boolean_paid(self) -> None: event = self._event( "payment_received", { @@ -320,18 +325,38 @@ def test_friendly_boolean_paid(self): ) self.assertEqual(event.description, "Marked as paid") - def test_friendly_boolean_collected(self): + def test_friendly_checklist_confirmation(self) -> None: event = self._event( - "job_collected", + "completion_checklist_updated", { "changes": [ - {"field_name": "Collected", "old_value": "No", "new_value": "Yes"} + { + "field_name": "Foreman sign-off", + "old_value": "No", + "new_value": "Yes", + } ] }, ) - self.assertEqual(event.description, "Marked as collected") + self.assertEqual(event.description, "Foreman signed the job off") - def test_long_text_field_truncates(self): + def test_friendly_checklist_withdrawal(self) -> None: + """Untickings are the changes worth finding later, so they read plainly.""" + event = self._event( + "completion_checklist_updated", + { + "changes": [ + { + "field_name": "Job released", + "old_value": "Yes", + "new_value": "No", + } + ] + }, + ) + self.assertEqual(event.description, "Job release withdrawn") + + def test_long_text_field_truncates(self) -> None: long_value = "x" * 200 event = self._event( "job_updated", @@ -350,7 +375,7 @@ def test_long_text_field_truncates(self): # Truncated value should be much shorter than the original self.assertLess(len(rendered), len(long_value) + 50) - def test_default_descriptor_for_unknown_field(self): + def test_default_descriptor_for_unknown_field(self) -> None: event = self._event( "job_updated", { @@ -361,7 +386,7 @@ def test_default_descriptor_for_unknown_field(self): ) self.assertEqual(event.description, "Some field changed from 'A' to 'B'") - def test_multiple_changes_join_with_period(self): + def test_multiple_changes_join_with_period(self) -> None: event = self._event( "job_updated", { @@ -380,7 +405,7 @@ def test_multiple_changes_join_with_period(self): "Marked as paid. Order number changed from 'PO-1' to 'PO-2'", ) - def test_priority_changed_within_column(self): + def test_priority_changed_within_column(self) -> None: event = self._event( "priority_changed", { @@ -406,7 +431,7 @@ def test_priority_changed_within_column(self): "Priority increased from 11th to 5th of 51 in In Progress", ) - def test_priority_changed_within_column_decreased(self): + def test_priority_changed_within_column_decreased(self) -> None: event = self._event( "priority_changed", { @@ -432,7 +457,7 @@ def test_priority_changed_within_column_decreased(self): "Priority decreased from 5th to 11th of 51 in In Progress", ) - def test_priority_changed_cross_column(self): + def test_priority_changed_cross_column(self) -> None: event = self._event( "priority_changed", { @@ -458,7 +483,7 @@ def test_priority_changed_cross_column(self): "Moved from In Progress (11th of 51) to Quoting (3rd of 27)", ) - def test_priority_legacy_float_diff_increased(self): + def test_priority_legacy_float_diff_increased(self) -> None: event = self._event( "priority_changed", { @@ -473,7 +498,7 @@ def test_priority_legacy_float_diff_increased(self): ) self.assertEqual(event.description, "Priority increased") - def test_priority_legacy_float_diff_decreased(self): + def test_priority_legacy_float_diff_decreased(self) -> None: event = self._event( "priority_changed", { @@ -488,7 +513,7 @@ def test_priority_legacy_float_diff_decreased(self): ) self.assertEqual(event.description, "Priority decreased") - def test_priority_legacy_unparseable_floats_returns_safe_text(self): + def test_priority_legacy_unparseable_floats_returns_safe_text(self) -> None: event = self._event( "priority_changed", { @@ -499,13 +524,13 @@ def test_priority_legacy_unparseable_floats_returns_safe_text(self): ) self.assertEqual(event.description, "Priority changed") - def test_helper_truncate_short_text_unchanged(self): + def test_helper_truncate_short_text_unchanged(self) -> None: self.assertEqual(_truncate("hello"), "hello") - def test_helper_truncate_long_text(self): + def test_helper_truncate_long_text(self) -> None: self.assertEqual(_truncate("a" * 100, 10), "aaaaaaaaa…") - def test_helper_format_ordinal(self): + def test_helper_format_ordinal(self) -> None: self.assertEqual(_format_ordinal(1), "1st") self.assertEqual(_format_ordinal(2), "2nd") self.assertEqual(_format_ordinal(3), "3rd") @@ -516,7 +541,7 @@ def test_helper_format_ordinal(self): self.assertEqual(_format_ordinal(21), "21st") self.assertEqual(_format_ordinal(22), "22nd") - def test_helper_truthy(self): + def test_helper_truthy(self) -> None: self.assertTrue(_truthy(True)) self.assertTrue(_truthy("Yes")) self.assertTrue(_truthy("yes")) @@ -530,7 +555,7 @@ def test_helper_truthy(self): class PriorityPositionCaptureTest(BaseTestCase): """Verify KanbanService.reorder_job attaches detail.position to the JobEvent.""" - def setUp(self): + def setUp(self) -> None: self.user = Staff.objects.create_user( email="kanban@example.com", password="testpass123", @@ -544,7 +569,7 @@ def setUp(self): ) self.xero_pay_item = XeroPayItem.get_ordinary_time() - def _make_job(self, name, status="in_progress"): + def _make_job(self, name: str, status: str = "in_progress") -> Job: job = Job( name=name, company=self.client_obj, @@ -555,7 +580,7 @@ def _make_job(self, name, status="in_progress"): job.save(staff=self.user) return job - def test_within_column_drag_records_position(self): + def test_within_column_drag_records_position(self) -> None: from apps.job.services.kanban_service import KanbanService job_a = self._make_job("Job A") @@ -585,7 +610,7 @@ def test_within_column_drag_records_position(self): self.assertEqual(position["old_position"], 3) # was at the bottom self.assertEqual(position["new_position"], 1) # now at the top - def test_noop_priority_change_creates_no_event(self): + def test_noop_priority_change_creates_no_event(self) -> None: """When priority_position has equal old/new positions, no JobEvent is created.""" job = self._make_job("Solo") before_count = JobEvent.objects.filter( @@ -612,7 +637,7 @@ def test_noop_priority_change_creates_no_event(self): ).count() self.assertEqual(after_count, before_count) - def test_cross_column_drag_records_old_and_new_totals(self): + def test_cross_column_drag_records_old_and_new_totals(self) -> None: from apps.job.services.kanban_service import KanbanService in_progress_job = self._make_job("Mover", status="in_progress") diff --git a/apps/job/tests/test_job_files_api.py b/apps/job/tests/test_job_files_api.py index 6a5af452b..5e11c51ea 100644 --- a/apps/job/tests/test_job_files_api.py +++ b/apps/job/tests/test_job_files_api.py @@ -18,7 +18,7 @@ class JobFilesApiTests(BaseAPITestCase): upload, find, or download job files. """ - def setUp(self): + def setUp(self) -> None: super().setUp() self._tmp_dropbox = tempfile.mkdtemp(prefix="dw-job-files-api-test-") self._settings_override = override_settings( @@ -46,12 +46,14 @@ def setUp(self): self.api = APIClient() self.api.force_authenticate(user=self.office_staff) - def tearDown(self): + def tearDown(self) -> None: self._settings_override.disable() shutil.rmtree(self._tmp_dropbox, ignore_errors=True) super().tearDown() - def test_upload_returns_full_job_file_and_file_is_immediately_available(self): + def test_upload_returns_full_job_file_and_file_is_immediately_available( + self, + ) -> None: payload = b"job attachment contents" file_obj = SimpleUploadedFile( "attachment.txt", @@ -82,7 +84,7 @@ def test_upload_returns_full_job_file_and_file_is_immediately_available(self): self.assertEqual(download_response.status_code, 200) self.assertEqual(b"".join(download_response.streaming_content), payload) - def test_upload_accepts_twenty_megabyte_attachment(self): + def test_upload_accepts_twenty_megabyte_attachment(self) -> None: payload = b"7" * (20 * 1024 * 1024) file_obj = SimpleUploadedFile( "large-attachment.txt", diff --git a/apps/job/tests/test_job_invoicing.py b/apps/job/tests/test_job_invoicing.py index 55ddae0b8..13d6f4087 100644 --- a/apps/job/tests/test_job_invoicing.py +++ b/apps/job/tests/test_job_invoicing.py @@ -9,7 +9,7 @@ from apps.accounting.models.invoice import Invoice from apps.company.models import Company from apps.job.models import Job -from apps.job.models.costing import CostLine +from apps.job.models.costing import CostLine, CostSet from apps.job.services.job_service import recalculate_job_invoicing_state from apps.testing import BaseTestCase @@ -17,13 +17,13 @@ class TestRecalculateJobInvoicingState(BaseTestCase): """Tests for recalculate_job_invoicing_state().""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Test Company", xero_last_modified=timezone.now(), ) - def _create_job(self, pricing_methodology="time_materials"): + def _create_job(self, pricing_methodology: str = "time_materials") -> Job: """Create a job. Job.save() auto-creates CostSets (actual, quote, estimate).""" job = Job( company=self.client_obj, @@ -33,7 +33,7 @@ def _create_job(self, pricing_methodology="time_materials"): job.save(staff=self.test_staff) return job - def _add_revenue_line(self, cost_set, revenue): + def _add_revenue_line(self, cost_set: CostSet, revenue: Decimal) -> None: """Add a CostLine with the given revenue to an existing CostSet.""" CostLine.objects.create( cost_set=cost_set, @@ -45,7 +45,9 @@ def _add_revenue_line(self, cost_set, revenue): accounting_date=date.today(), ) - def _create_invoice(self, job, amount, status="AUTHORISED"): + def _create_invoice( + self, job: Job, amount: Decimal, status: str = "AUTHORISED" + ) -> Invoice: """Create an invoice for the given job.""" return Invoice.objects.create( job=job, @@ -64,7 +66,7 @@ def _create_invoice(self, job, amount, status="AUTHORISED"): # --- T&M tests --- - def test_tm_fully_invoiced_when_invoiced_equals_actual(self): + def test_tm_fully_invoiced_when_invoiced_equals_actual(self) -> None: """T&M job is fully invoiced when invoiced amount equals actual revenue.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) @@ -75,7 +77,7 @@ def test_tm_fully_invoiced_when_invoiced_equals_actual(self): job.refresh_from_db() self.assertTrue(job.fully_invoiced) - def test_tm_fully_invoiced_when_invoiced_exceeds_actual(self): + def test_tm_fully_invoiced_when_invoiced_exceeds_actual(self) -> None: """T&M job is fully invoiced when invoiced amount exceeds actual revenue.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) @@ -86,7 +88,7 @@ def test_tm_fully_invoiced_when_invoiced_exceeds_actual(self): job.refresh_from_db() self.assertTrue(job.fully_invoiced) - def test_tm_not_fully_invoiced_when_invoiced_less_than_actual(self): + def test_tm_not_fully_invoiced_when_invoiced_less_than_actual(self) -> None: """T&M job is not fully invoiced when invoiced less than actual revenue.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) @@ -99,7 +101,7 @@ def test_tm_not_fully_invoiced_when_invoiced_less_than_actual(self): # --- Fixed-price tests --- - def test_fixed_price_fully_invoiced_when_invoiced_equals_quote(self): + def test_fixed_price_fully_invoiced_when_invoiced_equals_quote(self) -> None: """Fixed-price job is fully invoiced when invoiced matches quote revenue.""" job = self._create_job("fixed_price") self._add_revenue_line(job.latest_actual, Decimal("800.00")) @@ -111,7 +113,7 @@ def test_fixed_price_fully_invoiced_when_invoiced_equals_quote(self): job.refresh_from_db() self.assertTrue(job.fully_invoiced) - def test_fixed_price_not_fully_invoiced_even_if_exceeds_actual(self): + def test_fixed_price_not_fully_invoiced_even_if_exceeds_actual(self) -> None: """Fixed-price job is NOT fully invoiced when invoiced >= actual but < quote.""" job = self._create_job("fixed_price") self._add_revenue_line(job.latest_actual, Decimal("800.00")) @@ -127,7 +129,7 @@ def test_fixed_price_not_fully_invoiced_even_if_exceeds_actual(self): # --- Edge cases --- - def test_not_fully_invoiced_with_no_invoices(self): + def test_not_fully_invoiced_with_no_invoices(self) -> None: """Job with no invoices is not fully invoiced.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) @@ -137,7 +139,7 @@ def test_not_fully_invoiced_with_no_invoices(self): job.refresh_from_db() self.assertFalse(job.fully_invoiced) - def test_voided_invoices_excluded(self): + def test_voided_invoices_excluded(self) -> None: """Voided invoices should not count toward total invoiced.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) @@ -148,7 +150,7 @@ def test_voided_invoices_excluded(self): job.refresh_from_db() self.assertFalse(job.fully_invoiced) - def test_deleted_invoices_excluded(self): + def test_deleted_invoices_excluded(self) -> None: """Deleted invoices should not count toward total invoiced.""" job = self._create_job("time_materials") self._add_revenue_line(job.latest_actual, Decimal("1000.00")) diff --git a/apps/job/tests/test_job_latest_costsets.py b/apps/job/tests/test_job_latest_costsets.py index 35fbb2c89..78066268c 100644 --- a/apps/job/tests/test_job_latest_costsets.py +++ b/apps/job/tests/test_job_latest_costsets.py @@ -9,7 +9,7 @@ class JobLatestCostSetCreationTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Latest CostSet Company", xero_last_modified="2024-01-01T00:00:00Z", @@ -34,7 +34,7 @@ def _assert_initial_cost_sets(self, job: Job) -> None: self.assertEqual(job.cost_sets.filter(kind="quote").count(), 1) self.assertEqual(job.cost_sets.filter(kind="actual").count(), 1) - def test_manager_create_seeds_required_latest_cost_sets(self): + def test_manager_create_seeds_required_latest_cost_sets(self) -> None: job = Job.objects.create( company=self.client_obj, name="Manager create job", @@ -43,7 +43,7 @@ def test_manager_create_seeds_required_latest_cost_sets(self): self._assert_initial_cost_sets(job) - def test_model_save_seeds_required_latest_cost_sets(self): + def test_model_save_seeds_required_latest_cost_sets(self) -> None: job = Job(company=self.client_obj, name="Model save job") job.save(staff=self.test_staff) @@ -51,7 +51,7 @@ def test_model_save_seeds_required_latest_cost_sets(self): class JobLatestCostSetDeletionTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Latest CostSet Deletion Company", xero_last_modified="2024-01-01T00:00:00Z", @@ -62,13 +62,13 @@ def setUp(self): staff=self.test_staff, ) - def test_latest_cost_set_cannot_be_deleted_directly(self): + def test_latest_cost_set_cannot_be_deleted_directly(self) -> None: with self.assertRaises(RestrictedError): self.job.latest_quote.delete() self.assertTrue(CostSet.objects.filter(id=self.job.latest_quote_id).exists()) - def test_deleting_job_cascades_cost_sets(self): + def test_deleting_job_cascades_cost_sets(self) -> None: cost_set_ids = list(self.job.cost_sets.values_list("id", flat=True)) self.job.delete() diff --git a/apps/job/tests/test_job_quote_chat_model.py b/apps/job/tests/test_job_quote_chat_model.py index b93cc4720..6bdca8476 100644 --- a/apps/job/tests/test_job_quote_chat_model.py +++ b/apps/job/tests/test_job_quote_chat_model.py @@ -14,7 +14,7 @@ class JobQuoteChatModelTests(BaseTestCase): """Test JobQuoteChat model constraints""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.company_defaults = CompanyDefaults.get_solo() @@ -34,7 +34,7 @@ def setUp(self): staff=self.test_staff, ) - def test_message_id_uniqueness(self): + def test_message_id_uniqueness(self) -> None: """Test that message_id must be unique""" JobQuoteChat.objects.create( job=self.job, diff --git a/apps/job/tests/test_job_rest_error_mapping.py b/apps/job/tests/test_job_rest_error_mapping.py index f31740b54..7d5ff74e8 100644 --- a/apps/job/tests/test_job_rest_error_mapping.py +++ b/apps/job/tests/test_job_rest_error_mapping.py @@ -10,16 +10,19 @@ through the generated client, where `error` is a required key. """ +from uuid import UUID + from django.db import IntegrityError from django.http import Http404 from rest_framework import status from rest_framework.exceptions import NotFound from apps.accounting.services.invoice_calculation import InvoiceCalculationError -from apps.job.services.job_rest_service import DeltaValidationError, PreconditionFailed +from apps.job.services.job_rest_service import DeltaValidationError from apps.job.views.job_rest_views import BaseJobRestView from apps.purchasing.services.allocation_service import AllocationDeletionError from apps.testing import BaseTestCase +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.models import AppError @@ -29,17 +32,18 @@ def setUp(self) -> None: self.view = BaseJobRestView() def assert_maps_to( - self, error: Exception, expected_status: int, expected_body: dict[str, str] + self, error: Exception, expected_status: int, expected_error: str ) -> None: response = self.view.handle_service_error(error) self.assertEqual(response.status_code, expected_status) - self.assertEqual(response.data, expected_body) + self.assertEqual(response.data["error"], expected_error) + UUID(str(response.data["details"]["error_id"])) def test_value_error_is_a_bad_request(self) -> None: self.assert_maps_to( ValueError("Job with id abc not found"), status.HTTP_400_BAD_REQUEST, - {"error": "Job with id abc not found"}, + "Job with id abc not found", ) def test_value_error_subclasses_are_bad_requests_not_server_errors(self) -> None: @@ -52,31 +56,23 @@ def test_value_error_subclasses_are_bad_requests_not_server_errors(self) -> None self.assert_maps_to( error, status.HTTP_400_BAD_REQUEST, - {"error": str(error)}, + str(error), ) def test_precondition_failed_is_412(self) -> None: self.assert_maps_to( - PreconditionFailed("etag mismatch"), + PreconditionFailedError("etag mismatch"), status.HTTP_412_PRECONDITION_FAILED, - { - "error": ( - "Precondition failed (ETag mismatch). Reload the job and retry." - ) - }, + "Precondition failed (ETag mismatch). Reload the job and retry.", ) def test_delta_validation_error_is_412_not_shadowed(self) -> None: - """DeltaValidationError subclasses PreconditionFailed; it must not + """DeltaValidationError subclasses PreconditionFailedError; it must not fall through to the generic ValueError or default arm.""" self.assert_maps_to( DeltaValidationError("checksum mismatch"), status.HTTP_412_PRECONDITION_FAILED, - { - "error": ( - "Precondition failed (ETag mismatch). Reload the job and retry." - ) - }, + "Precondition failed (ETag mismatch). Reload the job and retry.", ) def test_not_found_variants_are_404(self) -> None: @@ -85,28 +81,28 @@ def test_not_found_variants_are_404(self) -> None: self.assert_maps_to( error, status.HTTP_404_NOT_FOUND, - {"error": "Resource not found"}, + "gone", ) def test_integrity_error_is_409(self) -> None: self.assert_maps_to( IntegrityError("duplicate key"), status.HTTP_409_CONFLICT, - {"error": "Duplicate event prevented by database constraint"}, + "duplicate key", ) def test_permission_error_is_403(self) -> None: self.assert_maps_to( PermissionError("not allowed"), status.HTTP_403_FORBIDDEN, - {"error": "not allowed"}, + "not allowed", ) - def test_unmapped_exception_is_500_without_leaking_the_message(self) -> None: + def test_unmapped_exception_is_500_with_persisted_message_and_id(self) -> None: self.assert_maps_to( RuntimeError("internal detail that should not ship"), status.HTTP_500_INTERNAL_SERVER_ERROR, - {"error": "Internal server error"}, + "internal detail that should not ship", ) diff --git a/apps/job/tests/test_job_rest_service.py b/apps/job/tests/test_job_rest_service.py index 8175cb221..44e4bf4dd 100644 --- a/apps/job/tests/test_job_rest_service.py +++ b/apps/job/tests/test_job_rest_service.py @@ -4,6 +4,7 @@ from django.utils import timezone from apps.company.models import Company, Person +from apps.job.etag import generate_job_etag from apps.job.models import Job, JobDeltaRejection, JobEvent from apps.job.models.costing import CostLine from apps.job.services.job_rest_service import DeltaValidationError, JobRestService @@ -12,7 +13,9 @@ class JobRestServiceCreateJobTests(BaseTestCase): - def test_fixed_price_create_copies_estimate_pay_item_without_relation_loads(self): + def test_fixed_price_create_copies_estimate_pay_item_without_relation_loads( + self, + ) -> None: company = Company.objects.create( name="Create Job Company", xero_last_modified=timezone.now(), @@ -45,7 +48,7 @@ def test_fixed_price_create_copies_estimate_pay_item_without_relation_loads(self class JobRestServiceEditTests(BaseTestCase): - def test_get_job_for_edit_serializes_event_staff(self): + def test_get_job_for_edit_serializes_event_staff(self) -> None: company = Company.objects.create( name="Edit Job Company", xero_last_modified=timezone.now(), @@ -75,7 +78,7 @@ class JobRestServiceDeltaRejectionRecordingTests(BaseTestCase): def test_hard_checksum_mismatch_records_the_rejection(self) -> None: """A refused delta must leave a JobDeltaRejection explaining why. - ``DeltaValidationError`` subclasses ``PreconditionFailed``. If the + ``DeltaValidationError`` subclasses ``PreconditionFailedError``. If the broader handler is ordered first it swallows the specific one, and because ``soft_fail_context`` is still None that early in the update the rejection is dropped entirely — the only record of why a client's @@ -106,7 +109,12 @@ def test_hard_checksum_mismatch_records_the_rejection(self) -> None: } with self.assertRaises(DeltaValidationError): - JobRestService.update_job(job.id, payload, self.test_staff) + JobRestService.update_job( + job.id, + payload, + self.test_staff, + generate_job_etag(job), + ) rejection = JobDeltaRejection.objects.get() self.assertIn("checksum mismatch", rejection.reason.lower()) diff --git a/apps/job/tests/test_kanban_changes.py b/apps/job/tests/test_kanban_changes.py new file mode 100644 index 000000000..3fbf45031 --- /dev/null +++ b/apps/job/tests/test_kanban_changes.py @@ -0,0 +1,266 @@ +"""Incremental Kanban freshness must update only the affected board structure.""" + +from datetime import timedelta +from typing import Protocol, TypedDict + +from django.utils import timezone + +from apps.accounts.models import Staff +from apps.company.models import Company +from apps.job.etag import generate_job_etag +from apps.job.models import Job +from apps.job.serializers.kanban_serializer import KanbanChangesResponseSerializer +from apps.testing import BaseAPITestCase +from apps.workflow.models import AppError, XeroPayItem + + +class KanbanCardPayload(TypedDict): + id: str + name: str + + +class KanbanChangesPayload(TypedDict): + success: bool + jobs: list[KanbanCardPayload] + removed_job_ids: list[str] + full_refresh_required: bool + + +class KanbanChangesTestResponse(Protocol): + status_code: int + + def json(self) -> KanbanChangesPayload: ... + + +class KanbanChangesAPITests(BaseAPITestCase): + def setUp(self) -> None: + super().setUp() + self.client.force_authenticate(self.test_staff) + self.company = Company.objects.create( + name="Kanban Changes Company", + xero_last_modified=timezone.now(), + ) + self.pay_item = XeroPayItem.get_ordinary_time() + self.first_job = self._create_job("First changed-card job", 9101) + self.second_job = self._create_job("Second changed-card job", 9102) + + def _create_job(self, name: str, job_number: int) -> Job: + created_job: object = Job.objects.create( + name=name, + job_number=job_number, + company=self.company, + status="in_progress", + created_by=self.test_staff, + default_xero_pay_item=self.pay_item, + staff=self.test_staff, + ) + if not isinstance(created_job, Job): + raise TypeError("Job manager returned a non-Job instance") + return created_job + + def _kanban_version(self) -> str: + response = self.client.get("/api/data-versions/") + self.assertEqual(response.status_code, 200) + payload: object = response.json() + if not isinstance(payload, dict): + self.fail("Data versions response must be an object") + kanban_version: object = payload.get("kanban") + if not isinstance(kanban_version, str): + self.fail("Data versions response must contain a string Kanban version") + return kanban_version + + def _changes_after(self, version: str) -> KanbanChangesTestResponse: + return self.client.get( + "/api/job/jobs/kanban-changes/", + {"after": version}, + ) + + def test_returns_only_the_changed_card_when_membership_is_unchanged(self) -> None: + version = self._kanban_version() + self.first_job.name = "Renamed first job" + self.first_job.save(staff=self.test_staff, update_fields=["name"]) + + response = self._changes_after(version) + + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertFalse(payload["full_refresh_required"]) + self.assertEqual( + [job["id"] for job in payload["jobs"]], + [str(self.first_job.id)], + ) + self.assertEqual(payload["jobs"][0]["name"], "Renamed first job") + self.assertEqual(payload["removed_job_ids"], []) + + def test_reports_a_card_removed_when_it_moves_to_hidden_status(self) -> None: + version = self._kanban_version() + self.first_job.status = "special" + self.first_job.save(staff=self.test_staff, update_fields=["status"]) + + response = self._changes_after(version) + + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertFalse(payload["full_refresh_required"]) + self.assertEqual(payload["jobs"], []) + self.assertEqual(payload["removed_job_ids"], [str(self.first_job.id)]) + + def test_returns_a_card_when_it_moves_to_the_archived_column(self) -> None: + version = self._kanban_version() + self.first_job.status = "archived" + self.first_job.save(staff=self.test_staff, update_fields=["status"]) + + response = self._changes_after(version) + + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertFalse(payload["full_refresh_required"]) + self.assertEqual( + [job["id"] for job in payload["jobs"]], + [str(self.first_job.id)], + ) + self.assertEqual(payload["removed_job_ids"], []) + + def test_requires_full_refresh_when_job_count_changes(self) -> None: + version = self._kanban_version() + self._create_job("Newly created job", 9103) + + response = self._changes_after(version) + + self.assertEqual(response.status_code, 200) + payload = response.json() + self.assertTrue(payload["full_refresh_required"]) + self.assertEqual(payload["jobs"], []) + self.assertEqual(payload["removed_job_ids"], []) + + def test_requires_full_refresh_for_count_neutral_delete_and_create(self) -> None: + version = self._kanban_version() + self.first_job.delete() + replacement = self._create_job("Replacement job", 9103) + + response = self._changes_after(version) + + self.assertEqual(Job.objects.count(), 2) + self.assertTrue(response.json()["full_refresh_required"]) + self.assertTrue(Job.objects.filter(pk=replacement.pk).exists()) + + def test_rejects_missing_or_malformed_version(self) -> None: + missing = self.client.get("/api/job/jobs/kanban-changes/") + malformed = self.client.get( + "/api/job/jobs/kanban-changes/", + {"after": "not-a-version"}, + ) + + self.assertEqual(missing.status_code, 400) + self.assertEqual(malformed.status_code, 400) + self.assertIn("error_id", malformed.json()["details"]) + self.assertEqual(AppError.objects.count(), 1) + + def test_success_is_required_by_the_response_contract(self) -> None: + serializer = KanbanChangesResponseSerializer( + data={ + "jobs": [], + "removed_job_ids": [], + "full_refresh_required": False, + } + ) + + self.assertFalse(serializer.is_valid()) + self.assertEqual(serializer.errors["success"][0].code, "required") + + +class ManualJobEventETagTests(BaseAPITestCase): + def setUp(self) -> None: + super().setUp() + self.office_staff = Staff.objects.create_user( + email="etag-office@example.test", + password="testpass", + first_name="ETag", + last_name="Office", + is_office_staff=True, + ) + self.client.force_authenticate(self.office_staff) + company = Company.objects.create( + name="ETag Event Company", + xero_last_modified=timezone.now(), + ) + self.job = Job.objects.create( + name="ETag Event Job", + job_number=9201, + company=company, + status="in_progress", + created_by=self.office_staff, + default_xero_pay_item=XeroPayItem.get_ordinary_time(), + staff=self.office_staff, + ) + self.detail_url = f"/api/job/jobs/{self.job.id}/" + self.event_url = f"/api/job/jobs/{self.job.id}/events/create/" + + def _current_etag(self) -> str: + response = self.client.get(self.detail_url) + self.assertEqual(response.status_code, 200) + return response["ETag"] + + def test_event_requires_if_match(self) -> None: + response = self.client.post( + self.event_url, + {"description": "No precondition"}, + format="json", + ) + + self.assertEqual(response.status_code, 428) + self.assertEqual(self.job.events.filter(event_type="manual_note").count(), 0) + + def test_event_rejects_stale_etag_without_creating_an_event(self) -> None: + stale_etag = self._current_etag() + self.job.name = "Concurrent rename" + self.job.save(staff=self.office_staff, update_fields=["name"]) + + response = self.client.post( + self.event_url, + {"description": "Stale note"}, + format="json", + HTTP_IF_MATCH=stale_etag, + ) + + self.assertEqual(response.status_code, 412) + self.assertEqual(self.job.events.filter(event_type="manual_note").count(), 0) + + def test_event_bumps_job_etag_and_updated_at(self) -> None: + original_updated_at = self.job.updated_at + original_etag = self._current_etag() + + response = self.client.post( + self.event_url, + {"description": "Fresh note"}, + format="json", + HTTP_IF_MATCH=original_etag, + ) + + self.assertEqual(response.status_code, 201) + self.job.refresh_from_db() + self.assertGreater(self.job.updated_at, original_updated_at) + self.assertNotEqual(response["ETag"], original_etag) + self.assertFalse(response["ETag"].startswith("W/")) + + def test_event_rejects_a_weak_if_match_tag(self) -> None: + current_etag = self._current_etag() + + response = self.client.post( + self.event_url, + {"description": "Weak precondition"}, + format="json", + HTTP_IF_MATCH=f"W/{current_etag}", + ) + + self.assertEqual(response.status_code, 412) + self.assertIn("error_id", response.json()["details"]) + + def test_job_etag_distinguishes_changes_within_one_millisecond(self) -> None: + first_timestamp = self.job.updated_at + self.job.updated_at = first_timestamp + timedelta(microseconds=100) + + later_etag = generate_job_etag(self.job) + self.job.updated_at = first_timestamp + + self.assertNotEqual(generate_job_etag(self.job), later_etag) diff --git a/apps/job/tests/test_kanban_reorder_priority.py b/apps/job/tests/test_kanban_reorder_priority.py index 9dc1b1a3d..812dc9444 100644 --- a/apps/job/tests/test_kanban_reorder_priority.py +++ b/apps/job/tests/test_kanban_reorder_priority.py @@ -12,7 +12,7 @@ class KanbanReorderPriorityTest(BaseTestCase): - def setUp(self): + def setUp(self) -> None: self.user = User.objects.create_user( email="reorder@example.com", password="testpass123", @@ -26,7 +26,7 @@ def setUp(self): ) self.xero_pay_item = XeroPayItem.get_ordinary_time() - def _make_job(self, name, status="in_progress"): + def _make_job(self, name: str, status: str = "in_progress") -> Job: job = Job( name=name, company=self.company, @@ -37,14 +37,14 @@ def _make_job(self, name, status="in_progress"): job.save(staff=self.user) return job - def _ordered_names(self, status="in_progress"): + def _ordered_names(self, status: str = "in_progress") -> list[str]: return list( Job.objects.filter(status=status) .order_by("-priority", "-created_at") .values_list("name", flat=True) ) - def test_reorder_below_visible_anchor_uses_canonical_lower_neighbour(self): + def test_reorder_below_visible_anchor_uses_canonical_lower_neighbour(self) -> None: self._make_job("Bottom") self._make_job("Hidden") anchor = self._make_job("Anchor") @@ -68,7 +68,7 @@ def test_reorder_below_visible_anchor_uses_canonical_lower_neighbour(self): ) self.assertNotIn("800", event.description) - def test_reorder_above_visible_anchor(self): + def test_reorder_above_visible_anchor(self) -> None: anchor = self._make_job("Anchor") mover = self._make_job("Mover") self._make_job("Top") @@ -82,7 +82,7 @@ def test_reorder_above_visible_anchor(self): self.assertEqual(self._ordered_names(), ["Top", "Mover", "Anchor"]) - def test_reorder_without_anchor_places_at_top_of_target_status(self): + def test_reorder_without_anchor_places_at_top_of_target_status(self) -> None: mover = self._make_job("Mover", status="in_progress") KanbanService.reorder_job( @@ -94,7 +94,7 @@ def test_reorder_without_anchor_places_at_top_of_target_status(self): self.assertEqual(self._ordered_names("in_progress"), []) self.assertEqual(self._ordered_names("quoting"), ["Mover"]) - def test_rejects_self_anchor(self): + def test_rejects_self_anchor(self) -> None: mover = self._make_job("Mover") with self.assertRaisesMessage( @@ -107,7 +107,7 @@ def test_rejects_self_anchor(self): staff=self.user, ) - def test_rejects_anchor_in_different_status(self): + def test_rejects_anchor_in_different_status(self) -> None: mover = self._make_job("Mover", status="in_progress") anchor = self._make_job("Anchor", status="quoting") diff --git a/apps/job/tests/test_kanban_search.py b/apps/job/tests/test_kanban_search.py index ca10b5670..610eb5936 100644 --- a/apps/job/tests/test_kanban_search.py +++ b/apps/job/tests/test_kanban_search.py @@ -20,7 +20,7 @@ class KanbanSearchTest(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.xero_pay_item = XeroPayItem.get_ordinary_time() self.shop_company = self._make_client("Demo Company Shop") @@ -96,7 +96,9 @@ def _make_quote(self, job: Job, *, number: str) -> Quote: raw_json={}, ) - def test_perform_advanced_search_matches_single_token_job_name_substring(self): + def test_perform_advanced_search_matches_single_token_job_name_substring( + self, + ) -> None: """Catches quick search no longer finding job-name substrings.""" target = self._make_job( name="2 X 1.2MM S/S KICK PLATES 910MM (W) X 300MM (H)", @@ -111,7 +113,9 @@ def test_perform_advanced_search_matches_single_token_job_name_substring(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_serializes_without_lazy_relation_loads(self): + def test_perform_advanced_search_serializes_without_lazy_relation_loads( + self, + ) -> None: """ Catches search results that would lazy-load relations during API serialization (the batched context must cover everything it reads). @@ -157,7 +161,7 @@ def test_search_with_results_does_not_trip_unused_eager_load_guard(self) -> None self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_preloads_quote_for_ranking(self): + def test_perform_advanced_search_preloads_quote_for_ranking(self) -> None: """Catches quote ranking that re-queries quotes per candidate job.""" target = self._make_job( name="Cool Awnings", @@ -183,7 +187,7 @@ def test_perform_advanced_search_preloads_quote_for_ranking(self): ] self.assertEqual(direct_quote_queries, []) - def test_perform_advanced_search_matches_quote_number(self): + def test_perform_advanced_search_matches_quote_number(self) -> None: """Catches quote-number search no longer finding the owning job.""" target = self._make_job( name="Cool Awnings", @@ -202,7 +206,7 @@ def test_perform_advanced_search_matches_quote_number(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_matches_numeric_substring(self): + def test_perform_advanced_search_matches_numeric_substring(self) -> None: """Catches numeric quick search no longer matching job descriptions.""" target = self._make_job( name="2 X 1.2MM S/S KICK PLATES 910MM (W) X 300MM (H)", @@ -217,7 +221,9 @@ def test_perform_advanced_search_matches_numeric_substring(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_numeric_query_prefers_job_number_over_long_description_substring(self): + def test_numeric_query_prefers_job_number_over_long_description_substring( + self, + ) -> None: """Catches job-number matches being buried under description substrings.""" target = self._set_job_number( self._make_job( @@ -244,7 +250,9 @@ def test_numeric_query_prefers_job_number_over_long_description_substring(self): self.assertEqual(jobs[0].id, target.id) self.assertIn(description_match.id, [job.id for job in jobs]) - def test_get_jobs_by_kanban_column_exact_job_number_suppresses_distant_noise(self): + def test_get_jobs_by_kanban_column_exact_job_number_suppresses_distant_noise( + self, + ) -> None: """Catches exact job-number filtering returning nearby numeric noise.""" target = self._set_job_number( self._make_job( @@ -279,7 +287,7 @@ def test_get_jobs_by_kanban_column_exact_job_number_suppresses_distant_noise(sel def test_perform_advanced_search_keeps_plausible_short_job_number_match( self, - ): + ) -> None: """Catches short job-number suffix searches being over-pruned.""" near_match = self._set_job_number( self._make_job( @@ -298,7 +306,9 @@ def test_perform_advanced_search_keeps_plausible_short_job_number_match( self.assertIn(near_match.id, [job.id for job in jobs]) - def test_numeric_query_prefers_job_number_suffix_over_middle_substring(self): + def test_numeric_query_prefers_job_number_suffix_over_middle_substring( + self, + ) -> None: """Catches suffix job-number matches losing to less useful middle matches.""" suffix_match = self._set_job_number( self._make_job( @@ -328,7 +338,7 @@ def test_numeric_query_prefers_job_number_suffix_over_middle_substring(self): ) self.assertLess(middle_score, suffix_score) - def test_perform_advanced_search_keeps_multiple_close_text_matches(self): + def test_perform_advanced_search_keeps_multiple_close_text_matches(self) -> None: """Catches text search collapsing distinct plausible job matches.""" target_one = self._make_job( name="Kick plates", @@ -347,7 +357,7 @@ def test_perform_advanced_search_keeps_multiple_close_text_matches(self): self.assertEqual({job.id for job in jobs}, {target_one.id, target_two.id}) - def test_perform_advanced_search_matches_client_tokens_in_any_order(self): + def test_perform_advanced_search_matches_client_tokens_in_any_order(self) -> None: """Catches company-name token search becoming order-sensitive.""" target = self._make_job( name="Kick plates", @@ -366,7 +376,7 @@ def test_perform_advanced_search_matches_client_tokens_in_any_order(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_matches_person_name_substring(self): + def test_perform_advanced_search_matches_person_name_substring(self) -> None: """Catches contact-name search no longer matching partial names.""" target = self._make_job( name="Kick plates", @@ -383,7 +393,7 @@ def test_perform_advanced_search_matches_person_name_substring(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_get_jobs_by_kanban_column_matches_client_tokens_in_any_order(self): + def test_get_jobs_by_kanban_column_matches_client_tokens_in_any_order(self) -> None: """Catches kanban column search ignoring unordered company-name tokens.""" target = self._make_job( name="Kick plates", @@ -403,7 +413,7 @@ def test_get_jobs_by_kanban_column_matches_client_tokens_in_any_order(self): self.assertTrue(result["success"]) self.assertEqual([job["id"] for job in result["jobs"]], [str(target.id)]) - def test_perform_advanced_search_returns_empty_when_query_not_present(self): + def test_perform_advanced_search_returns_empty_when_query_not_present(self) -> None: """Catches unrelated jobs being returned for absent search terms.""" self._make_job( name="2 X 1.2MM S/S KICK PLATES 910MM (W) X 300MM (H)", @@ -416,7 +426,9 @@ def test_perform_advanced_search_returns_empty_when_query_not_present(self): self.assertEqual(jobs, []) - def test_perform_advanced_search_returns_empty_for_only_weak_trigram_matches(self): + def test_perform_advanced_search_returns_empty_for_only_weak_trigram_matches( + self, + ) -> None: """Catches weak fuzzy matches leaking below the display threshold.""" weak_match = self._make_job( name="5x swaged ends", @@ -435,7 +447,7 @@ def test_perform_advanced_search_returns_empty_for_only_weak_trigram_matches(sel self.assertEqual(ranked_jobs, []) - def test_perform_advanced_search_recovers_typo_tolerance(self): + def test_perform_advanced_search_recovers_typo_tolerance(self) -> None: """Catches typo tolerance no longer recovering misspelled company searches.""" target = self._make_job( name="2 X 1.2MM S/S KICK PLATES 910MM (W) X 300MM (H)", @@ -450,7 +462,7 @@ def test_perform_advanced_search_recovers_typo_tolerance(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_get_jobs_by_kanban_column_recovers_typo_tolerance(self): + def test_get_jobs_by_kanban_column_recovers_typo_tolerance(self) -> None: """Catches kanban column search losing typo tolerance.""" target = self._make_job( name="Kick plates", @@ -465,7 +477,7 @@ def test_get_jobs_by_kanban_column_recovers_typo_tolerance(self): self.assertTrue(result["success"]) self.assertEqual([job["id"] for job in result["jobs"]], [str(target.id)]) - def test_perform_advanced_search_does_not_fuzzy_match_invoice_numbers(self): + def test_perform_advanced_search_does_not_fuzzy_match_invoice_numbers(self) -> None: """Catches invoice searches fuzzily matching the wrong invoice.""" target = self._make_job( name="Kick plates", @@ -484,7 +496,9 @@ def test_perform_advanced_search_does_not_fuzzy_match_invoice_numbers(self): self.assertEqual(jobs, []) - def test_perform_advanced_search_matches_invoice_number_exactly_via_filter(self): + def test_perform_advanced_search_matches_invoice_number_exactly_via_filter( + self, + ) -> None: """Catches the invoice filter failing to match a full invoice number.""" target = self._make_job( name="Kick plates", @@ -503,7 +517,7 @@ def test_perform_advanced_search_matches_invoice_number_exactly_via_filter(self) self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_matches_bare_invoice_number(self): + def test_perform_advanced_search_matches_bare_invoice_number(self) -> None: """Catches the invoice filter failing to match bare invoice digits.""" target = self._make_job( name="Cool Awnings", @@ -522,7 +536,7 @@ def test_perform_advanced_search_matches_bare_invoice_number(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_unrecognised_invoice_returns_empty(self): + def test_perform_advanced_search_unrecognised_invoice_returns_empty(self) -> None: """Catches invalid invoice filters returning unrelated jobs.""" target = self._make_job( name="Kick plates", @@ -536,7 +550,7 @@ def test_perform_advanced_search_unrecognised_invoice_returns_empty(self): self.assertEqual(jobs, []) - def test_perform_advanced_search_quick_search_matches_order_number(self): + def test_perform_advanced_search_quick_search_matches_order_number(self) -> None: """Catches universal search no longer matching order numbers.""" target = self._make_job( name="Cool Awnings", @@ -553,7 +567,7 @@ def test_perform_advanced_search_quick_search_matches_order_number(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_order_number_filter(self): + def test_perform_advanced_search_order_number_filter(self) -> None: """Catches the explicit order-number filter returning the wrong job.""" target = self._make_job( name="Cool Awnings", @@ -570,7 +584,7 @@ def test_perform_advanced_search_order_number_filter(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_quick_search_matches_invoice_number(self): + def test_perform_advanced_search_quick_search_matches_invoice_number(self) -> None: """Catches universal search no longer matching full invoice numbers.""" target = self._make_job( name="Cool Awnings", @@ -589,7 +603,9 @@ def test_perform_advanced_search_quick_search_matches_invoice_number(self): self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_quick_search_matches_bare_invoice_number(self): + def test_perform_advanced_search_quick_search_matches_bare_invoice_number( + self, + ) -> None: """Catches universal search no longer matching bare invoice digits.""" target = self._make_job( name="Cool Awnings", @@ -610,7 +626,7 @@ def test_perform_advanced_search_quick_search_matches_bare_invoice_number(self): def test_perform_advanced_search_invoice_match_returns_job_once_with_multiple_invoices( self, - ): + ) -> None: """Catches invoice joins duplicating jobs with multiple matching invoices.""" target = self._make_job( name="Cool Awnings", @@ -627,7 +643,7 @@ def test_perform_advanced_search_invoice_match_returns_job_once_with_multiple_in def test_perform_advanced_search_text_match_returns_job_once_with_multiple_invoices( self, - ): + ) -> None: """Catches text search duplicating jobs that have multiple invoices.""" target = self._make_job( name="Cool Awnings", @@ -640,7 +656,7 @@ def test_perform_advanced_search_text_match_returns_job_once_with_multiple_invoi self.assertEqual([job.id for job in jobs], [target.id]) - def test_perform_advanced_search_invoice_reason_present(self): + def test_perform_advanced_search_invoice_reason_present(self) -> None: """Catches invoice matches losing their explainable search reason.""" target = self._make_job( name="Cool Awnings", @@ -657,7 +673,7 @@ def test_perform_advanced_search_invoice_reason_present(self): reason_names = [t.get("reason") for t in token_reasons] self.assertIn("invoice_contains", reason_names) - def test_kanban_search_logging_records_ranked_results_and_reasons(self): + def test_kanban_search_logging_records_ranked_results_and_reasons(self) -> None: """Catches search logs losing ranked result and scoring diagnostics.""" target = self._set_job_number( self._make_job( diff --git a/apps/job/tests/test_kanban_service.py b/apps/job/tests/test_kanban_service.py index e91b78b6f..eb7962498 100644 --- a/apps/job/tests/test_kanban_service.py +++ b/apps/job/tests/test_kanban_service.py @@ -10,6 +10,7 @@ from apps.accounts.models import Staff from apps.company.models import Company, CompanyPersonLink, Person from apps.job.models import Job +from apps.job.models.costing import CostSet from apps.job.services.kanban_service import KanbanService from apps.testing import BaseTestCase from apps.workflow.models import CompanyDefaults @@ -18,14 +19,16 @@ class TestSerializeJobForApi(BaseTestCase): """Tests for KanbanService kanban job serialization.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Test Company", xero_last_modified=timezone.now(), ) self.shop_company = CompanyDefaults.get_solo().shop_company - def _create_job(self, pricing_methodology="time_materials", name="Test Job"): + def _create_job( + self, pricing_methodology: str = "time_materials", name: str = "Test Job" + ) -> Job: """Create a job. Job.save() auto-creates CostSets (actual, quote, estimate).""" job = Job( company=self.client_obj, @@ -35,7 +38,7 @@ def _create_job(self, pricing_methodology="time_materials", name="Test Job"): job.save(staff=self.test_staff) return job - def _set_summary_revenue(self, cost_set, revenue): + def _set_summary_revenue(self, cost_set: CostSet, revenue: Decimal) -> None: """Set the precomputed revenue total that serialize_job_for_api reads.""" cost_set.summary = {"rev": float(revenue)} cost_set.save(update_fields=["summary"]) @@ -50,7 +53,7 @@ def _link(self, name: str) -> CompanyPersonLink: person=person, ) - def test_over_budget_when_tm_actual_exceeds_price_cap(self): + def test_over_budget_when_tm_actual_exceeds_price_cap(self) -> None: """T&M jobs show over-budget when actual revenue exceeds the price cap.""" job = self._create_job("time_materials") job.price_cap = Decimal("1000.00") @@ -60,7 +63,7 @@ def test_over_budget_when_tm_actual_exceeds_price_cap(self): self.assertTrue(self._serialize_one(job)["over_budget"]) - def test_not_over_budget_when_tm_actual_within_price_cap(self): + def test_not_over_budget_when_tm_actual_within_price_cap(self) -> None: """T&M jobs do not show over-budget when actual revenue stays within the cap.""" job = self._create_job("time_materials") job.price_cap = Decimal("1000.00") @@ -70,7 +73,7 @@ def test_not_over_budget_when_tm_actual_within_price_cap(self): self.assertFalse(self._serialize_one(job)["over_budget"]) - def test_fixed_price_uses_quote_revenue_threshold(self): + def test_fixed_price_uses_quote_revenue_threshold(self) -> None: """Fixed-price jobs compare actual revenue against quote revenue, not price cap.""" job = self._create_job("fixed_price") job.price_cap = Decimal("1000.00") @@ -80,7 +83,7 @@ def test_fixed_price_uses_quote_revenue_threshold(self): self.assertFalse(self._serialize_one(job)["over_budget"]) - def test_serialize_jobs_for_api_query_count_is_constant(self): + def test_serialize_jobs_for_api_query_count_is_constant(self) -> None: """Serialization batches related data: O(1) queries regardless of job count, and no per-job relation lazy loads (each one stalls the dev/E2E n+1 guard with a ~20ms stack inspection).""" @@ -109,7 +112,7 @@ def test_serialize_jobs_for_api_query_count_is_constant(self): ) self.assertLessEqual(len(captured_all.captured_queries), 6) - def test_serialized_shape_for_fully_populated_job(self): + def test_serialized_shape_for_fully_populated_job(self) -> None: """The rewritten serializer must keep the exact response contract the frontend Zod schema requires.""" job = self._create_job(name="Shape Job") @@ -182,7 +185,7 @@ def test_serialized_shape_for_fully_populated_job(self): ) self.assertIsNone(serialized["people"][0]["icon_url"]) - def test_serialize_jobs_for_api_resolves_shop_client_once_for_batch(self): + def test_serialize_jobs_for_api_resolves_shop_client_once_for_batch(self) -> None: shop_job = Job( company=self.shop_company, name="Shop Job", @@ -216,7 +219,7 @@ def test_serialize_jobs_for_api_resolves_shop_client_once_for_batch(self): def test_non_shop_job_serializes_false_when_client_differs_from_configured_shop( self, - ): + ) -> None: job = self._create_job() self._set_summary_revenue(job.latest_actual, Decimal("900.00")) self._set_summary_revenue(job.latest_quote, Decimal("500.00")) diff --git a/apps/job/tests/test_labour_subtypes.py b/apps/job/tests/test_labour_subtypes.py index 95e7bdcb7..95afaf0c5 100644 --- a/apps/job/tests/test_labour_subtypes.py +++ b/apps/job/tests/test_labour_subtypes.py @@ -158,7 +158,7 @@ def test_create_entry_uses_staff_default_subtype_and_job_rate(self) -> None: line = self.service.create_entry( { "job_id": str(self.job.id), - "description": "", + "description": None, "hours": Decimal("2.000"), "accounting_date": date.today(), } @@ -180,7 +180,7 @@ def test_create_entry_with_explicit_subtype_uses_that_jobs_subtype_rate( line = self.service.create_entry( { "job_id": str(self.job.id), - "description": "", + "description": None, "hours": Decimal("1.000"), "accounting_date": date.today(), "labour_subtype_id": str(onsite.id), @@ -198,7 +198,7 @@ def test_serializer_patch_of_subtype_alone_recalculates_rate(self) -> None: line = self.service.create_entry( { "job_id": str(self.job.id), - "description": "", + "description": None, "hours": Decimal("1.000"), "accounting_date": date.today(), } @@ -219,7 +219,7 @@ def test_update_entry_subtype_recalculates_rate(self) -> None: line = self.service.create_entry( { "job_id": str(self.job.id), - "description": "", + "description": None, "hours": Decimal("1.000"), "accounting_date": date.today(), } diff --git a/apps/job/tests/test_mcp_tool_integration.py b/apps/job/tests/test_mcp_tool_integration.py index a6b1109e8..cfd958cbc 100644 --- a/apps/job/tests/test_mcp_tool_integration.py +++ b/apps/job/tests/test_mcp_tool_integration.py @@ -4,7 +4,6 @@ Tests cover: - QuotingTool functionality - SupplierProductQueryTool functionality -- Tool parameter validation - Error handling in tool execution """ @@ -18,7 +17,7 @@ class QuotingToolTests(BaseTestCase): """Test QuotingTool functionality""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" # Create a supplier self.supplier = Company.objects.create( @@ -114,11 +113,7 @@ def setUp(self): self.tool = QuotingTool() - def test_tool_initialization(self): - """Test tool initializes correctly""" - self.assertIsInstance(self.tool, QuotingTool) - - def test_search_products_basic(self): + def test_search_products_basic(self) -> None: """Test basic product search functionality""" result = self.tool.search_products(query="steel") @@ -126,33 +121,33 @@ def test_search_products_basic(self): self.assertIn("Steel Plate", result) self.assertIn("ABC Steel", result) - def test_search_products_with_supplier(self): + def test_search_products_with_supplier(self) -> None: """Test product search with supplier filter""" result = self.tool.search_products(query="steel", supplier_name="ABC Steel") self.assertIn("ABC Steel", result) self.assertNotIn("XYZ Metals", result) - def test_search_products_no_results(self): + def test_search_products_no_results(self) -> None: """Test product search with no results""" result = self.tool.search_products(query="nonexistent_material_xyz") self.assertIn("No products found", result) - def test_search_products_case_insensitive(self): + def test_search_products_case_insensitive(self) -> None: """Test that product search is case insensitive""" result = self.tool.search_products(query="STEEL ANGLE") self.assertIn("Steel Angle", result) - def test_get_pricing_for_material(self): + def test_get_pricing_for_material(self) -> None: """Test getting pricing for specific material""" result = self.tool.get_pricing_for_material(material_type="steel") self.assertIn("steel", result.lower()) self.assertIn("ABC Steel", result) - def test_get_pricing_with_dimensions(self): + def test_get_pricing_with_dimensions(self) -> None: """Test getting pricing with dimensions filter""" result = self.tool.get_pricing_for_material( material_type="steel", dimensions="1200x2400" @@ -160,13 +155,13 @@ def test_get_pricing_with_dimensions(self): self.assertIn("1200x2400", result) - def test_get_pricing_no_results(self): + def test_get_pricing_no_results(self) -> None: """Test getting pricing with no matching materials""" result = self.tool.get_pricing_for_material(material_type="titanium") self.assertIn("No pricing found", result) - def test_create_quote_estimate(self): + def test_create_quote_estimate(self) -> None: """Test creating a quote estimate""" result = self.tool.create_quote_estimate( job_id=str(self.job.id), @@ -179,7 +174,7 @@ def test_create_quote_estimate(self): self.assertIn("Labor estimate", result) self.assertIn("15", result) - def test_create_quote_estimate_invalid_job(self): + def test_create_quote_estimate_invalid_job(self) -> None: """Test creating quote for non-existent job""" result = self.tool.create_quote_estimate( job_id="00000000-0000-0000-0000-000000000000", @@ -188,7 +183,7 @@ def test_create_quote_estimate_invalid_job(self): self.assertIn("not found", result) - def test_get_supplier_status(self): + def test_get_supplier_status(self) -> None: """Test getting supplier status""" result = self.tool.get_supplier_status() @@ -196,13 +191,13 @@ def test_get_supplier_status(self): self.assertIn("XYZ Metals", result) self.assertIn("Products:", result) - def test_get_supplier_status_filtered(self): + def test_get_supplier_status_filtered(self) -> None: """Test getting supplier status with filter""" result = self.tool.get_supplier_status(supplier_name="ABC") self.assertIn("ABC Steel", result) - def test_compare_suppliers(self): + def test_compare_suppliers(self) -> None: """Test comparing suppliers for same material""" result = self.tool.compare_suppliers(material_query="steel angle") @@ -210,13 +205,13 @@ def test_compare_suppliers(self): self.assertIn("XYZ Metals", result) self.assertIn("Comparison", result) - def test_compare_suppliers_no_results(self): + def test_compare_suppliers_no_results(self) -> None: """Test comparing suppliers with no matching products""" result = self.tool.compare_suppliers(material_query="nonexistent_xyz") self.assertIn("No products found", result) - def test_calc_sheet_tenths_basic(self): + def test_calc_sheet_tenths_basic(self) -> None: """Test basic sheet tenths calculation""" result = self.tool.calc_sheet_tenths( part_width_mm=600, @@ -227,7 +222,7 @@ def test_calc_sheet_tenths_basic(self): self.assertIn("600", result) self.assertIn("480", result) - def test_calc_sheet_tenths_larger_part(self): + def test_calc_sheet_tenths_larger_part(self) -> None: """Test sheet tenths for larger part spanning multiple sections""" result = self.tool.calc_sheet_tenths( part_width_mm=700, @@ -236,7 +231,7 @@ def test_calc_sheet_tenths_larger_part(self): self.assertIn("4 tenth", result.lower()) - def test_calc_sheet_tenths_custom_sheet_size(self): + def test_calc_sheet_tenths_custom_sheet_size(self) -> None: """Test sheet tenths with custom sheet dimensions""" result = self.tool.calc_sheet_tenths( part_width_mm=500, @@ -252,7 +247,7 @@ def test_calc_sheet_tenths_custom_sheet_size(self): class SupplierProductQueryToolTests(BaseTestCase): """Test SupplierProductQueryTool functionality""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.supplier = Company.objects.create( name="Test Supplier", @@ -277,20 +272,7 @@ def setUp(self): self.tool = SupplierProductQueryTool() - def test_tool_initialization(self): - """Test tool initializes correctly""" - self.assertIsInstance(self.tool, SupplierProductQueryTool) - - def test_model_attribute(self): - """Test that model is set to SupplierProduct""" - self.assertEqual(self.tool.model, SupplierProduct) - - def test_exclude_fields(self): - """Test that supplier and price_list are excluded""" - self.assertIn("supplier", self.tool.exclude_fields) - self.assertIn("price_list", self.tool.exclude_fields) - - def test_get_queryset(self): + def test_get_queryset(self) -> None: """Test that get_queryset returns products with related data""" queryset = self.tool.get_queryset() product = queryset.first() @@ -302,7 +284,7 @@ def test_get_queryset(self): class MCPToolIntegrationTests(BaseTestCase): """Test MCP tool integration behavior""" - def setUp(self): + def setUp(self) -> None: """Set up test data""" self.supplier = Company.objects.create( name="Integration Test Supplier", @@ -324,15 +306,7 @@ def setUp(self): staff=self.test_staff, ) - def test_tool_parameter_validation(self): - """Test tool parameter validation""" - tool = QuotingTool() - - # Test missing required parameters - with self.assertRaises(TypeError): - tool.search_products() # Missing query parameter - - def test_tool_response_is_string(self): + def test_tool_response_is_string(self) -> None: """Test that tools return string responses""" tool = QuotingTool() @@ -342,7 +316,7 @@ def test_tool_response_is_string(self): self.assertIsInstance(result, str) self.assertGreater(len(result), 0) - def test_tool_handles_special_characters(self): + def test_tool_handles_special_characters(self) -> None: """Test that tools handle special characters in queries""" tool = QuotingTool() @@ -350,7 +324,7 @@ def test_tool_handles_special_characters(self): result = tool.search_products(query="steel & aluminium 50%") self.assertIsInstance(result, str) - def test_tool_handles_empty_query(self): + def test_tool_handles_empty_query(self) -> None: """Test that tools handle empty queries gracefully""" tool = QuotingTool() @@ -361,11 +335,11 @@ def test_tool_handles_empty_query(self): class CalcSheetTenthsTests(BaseTestCase): """Dedicated tests for calc_sheet_tenths functionality""" - def setUp(self): + def setUp(self) -> None: """Set up test tool""" self.tool = QuotingTool() - def test_single_section(self): + def test_single_section(self) -> None: """Test part that fits in one section""" result = self.tool.calc_sheet_tenths( part_width_mm=500, @@ -373,7 +347,7 @@ def test_single_section(self): ) self.assertIn("1 tenth", result.lower()) - def test_two_sections_horizontal(self): + def test_two_sections_horizontal(self) -> None: """Test part spanning two sections horizontally""" result = self.tool.calc_sheet_tenths( part_width_mm=700, # > 600, spans 2 columns @@ -381,7 +355,7 @@ def test_two_sections_horizontal(self): ) self.assertIn("2 tenth", result.lower()) - def test_two_sections_vertical(self): + def test_two_sections_vertical(self) -> None: """Test part spanning two sections vertically""" result = self.tool.calc_sheet_tenths( part_width_mm=500, # fits in 1 column @@ -389,7 +363,7 @@ def test_two_sections_vertical(self): ) self.assertIn("2 tenth", result.lower()) - def test_four_sections(self): + def test_four_sections(self) -> None: """Test part spanning 2x2 sections""" result = self.tool.calc_sheet_tenths( part_width_mm=700, @@ -397,7 +371,7 @@ def test_four_sections(self): ) self.assertIn("4 tenth", result.lower()) - def test_full_sheet(self): + def test_full_sheet(self) -> None: """Test part using entire sheet""" result = self.tool.calc_sheet_tenths( part_width_mm=1200, @@ -405,7 +379,7 @@ def test_full_sheet(self): ) self.assertIn("10 tenth", result.lower()) - def test_oversized_part(self): + def test_oversized_part(self) -> None: """Test part larger than sheet""" result = self.tool.calc_sheet_tenths( part_width_mm=1500, diff --git a/apps/job/tests/test_modern_timesheet_views.py b/apps/job/tests/test_modern_timesheet_views.py index 2dcf31633..fc8bb72a1 100644 --- a/apps/job/tests/test_modern_timesheet_views.py +++ b/apps/job/tests/test_modern_timesheet_views.py @@ -14,7 +14,7 @@ class ModernTimesheetEntryQueryTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.target_date = date(2026, 5, 22) self.company = Company.objects.create( @@ -53,7 +53,7 @@ def setUp(self): entry_seq=1, ) - def test_timesheet_cost_line_serializer_uses_preloaded_relations(self): + def test_timesheet_cost_line_serializer_uses_preloaded_relations(self) -> None: cost_lines = ( CostLine.objects.filter(pk=self.cost_line.pk) .select_related( diff --git a/apps/job/tests/test_paid_flag_service.py b/apps/job/tests/test_paid_flag_service.py index e0099bd67..7c6b82894 100644 --- a/apps/job/tests/test_paid_flag_service.py +++ b/apps/job/tests/test_paid_flag_service.py @@ -16,7 +16,7 @@ class PaidFlagServiceTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Paid Flag Company", xero_last_modified=timezone.now(), @@ -48,7 +48,7 @@ def _create_invoice(self, job: Job, status: str) -> Invoice: raw_json={}, ) - def test_update_paid_flags_uses_prefetched_invoices_for_batch(self): + def test_update_paid_flags_uses_prefetched_invoices_for_batch(self) -> None: """Batch paid-flag checks must not lazy-load invoices per job.""" paid_job = self._create_job("Paid Job") unpaid_job = self._create_job("Unpaid Job") diff --git a/apps/job/tests/test_pdf_goldens.py b/apps/job/tests/test_pdf_goldens.py index 44541926c..787ec03b5 100644 --- a/apps/job/tests/test_pdf_goldens.py +++ b/apps/job/tests/test_pdf_goldens.py @@ -65,7 +65,7 @@ def setUp(self): self.job = build_golden_job(self.test_staff) - def test_delivery_docket_matches_golden(self): + def test_delivery_docket_matches_golden(self) -> None: pdf_buffer, _job_file = generate_delivery_docket( self.job, staff=self.test_staff ) @@ -73,7 +73,7 @@ def test_delivery_docket_matches_golden(self): expected = EXPECTED_DELIVERY_DOCKET.read_bytes() self.assertEqual(actual, expected, msg=REGEN_HINT) - def test_workshop_pdf_matches_golden(self): + def test_workshop_pdf_matches_golden(self) -> None: pdf_buffer = create_workshop_pdf(self.job) actual = pdf_buffer.getvalue() expected = EXPECTED_WORKSHOP.read_bytes() diff --git a/apps/job/tests/test_quote_modes.py b/apps/job/tests/test_quote_modes.py index 58161daab..8350e74a2 100644 --- a/apps/job/tests/test_quote_modes.py +++ b/apps/job/tests/test_quote_modes.py @@ -21,7 +21,7 @@ class TestQuoteModeSchemas(TestCase): """Test the JSON schemas for each mode.""" - def test_calc_schema_valid(self): + def test_calc_schema_valid(self) -> None: """Test valid CALC schema data.""" valid_data = { "inputs": { @@ -48,7 +48,7 @@ def test_calc_schema_valid(self): validate(instance=valid_data, schema=schema) - def test_calc_schema_invalid_missing_required(self): + def test_calc_schema_invalid_missing_required(self) -> None: """Test CALC schema validation with missing required fields.""" invalid_data = { "inputs": {"units": "mm"}, @@ -61,7 +61,7 @@ def test_calc_schema_invalid_missing_required(self): with self.assertRaises(ValidationError): validate(instance=invalid_data, schema=schema) - def test_price_schema_valid(self): + def test_price_schema_valid(self) -> None: """Test valid PRICE schema data.""" valid_data = { "normalized": { @@ -92,7 +92,7 @@ def test_price_schema_valid(self): validate(instance=valid_data, schema=schema) - def test_table_schema_valid(self): + def test_table_schema_valid(self) -> None: """Test valid TABLE schema data.""" valid_data = { "rows": [ @@ -121,7 +121,7 @@ def test_table_schema_valid(self): validate(instance=valid_data, schema=schema) - def test_get_allowed_tools(self): + def test_get_allowed_tools(self) -> None: """Test tool gating for each mode.""" # CALC mode should have sheet tenths tool and emit tool calc_tools = quote_mode_schemas.get_allowed_tools("CALC") @@ -142,7 +142,7 @@ def test_get_allowed_tools(self): class TestQuoteModeController(BaseTestCase): """Test the QuoteModeController functionality.""" - def setUp(self): + def setUp(self) -> None: """Set up test fixtures.""" self.controller = QuoteModeController() @@ -221,7 +221,7 @@ def test_mode_inference_table(self, mock_llm_class): mode = self.controller.infer_mode(input_text) self.assertEqual(mode, "TABLE", f"Failed to infer TABLE for: {input_text}") - def test_system_prompt(self): + def test_system_prompt(self) -> None: """Test that system prompt is appropriate for mode-based operation.""" prompt = self.controller.get_system_prompt() @@ -234,7 +234,7 @@ def test_system_prompt(self): self.assertIn("emit_table_result", prompt) self.assertIn("MUST call", prompt) - def test_render_prompt(self): + def test_render_prompt(self) -> None: """Test prompt rendering for each mode.""" # Test CALC prompt calc_prompt = self.controller.render_prompt( @@ -260,7 +260,7 @@ def test_render_prompt(self): self.assertIn("MODE=TABLE", table_prompt) self.assertIn("emit_table_result", table_prompt) - def test_validate_json(self): + def test_validate_json(self) -> None: """Test JSON validation against schemas.""" # Valid data should pass valid_calc_data = { @@ -281,7 +281,7 @@ def test_validate_json(self): with self.assertRaises(ValidationError): self.controller.validate_json(invalid_data, self.controller.schemas["CALC"]) - def test_get_mcp_tools_for_mode(self): + def test_get_mcp_tools_for_mode(self) -> None: """Test that correct tools are returned for each mode.""" # CALC mode - calc_sheet_tenths and emit tool calc_tools = self.controller.get_mcp_tools_for_mode("CALC") @@ -304,7 +304,7 @@ def test_get_mcp_tools_for_mode(self): self.assertEqual(len(table_tools), 1) self.assertEqual(table_tools[0]["function"]["name"], "emit_table_result") - def test_invalid_mode(self): + def test_invalid_mode(self) -> None: """Test that invalid mode raises ValueError.""" with self.assertRaises(ValueError) as context: self.controller.run(mode="INVALID", user_input="Test", job=None) @@ -314,7 +314,7 @@ def test_invalid_mode(self): class TestSheetTenthsIntegration(BaseTestCase): """Integration tests for sheet tenths calculation in CALC mode.""" - def setUp(self): + def setUp(self) -> None: """Set up test fixtures.""" # Create test job self.client_obj = Company.objects.create( diff --git a/apps/job/tests/test_workshop_pdf_service.py b/apps/job/tests/test_workshop_pdf_service.py index 781bd7ed2..d1907a67b 100644 --- a/apps/job/tests/test_workshop_pdf_service.py +++ b/apps/job/tests/test_workshop_pdf_service.py @@ -101,29 +101,29 @@ def test_annotated_job_falls_back_to_client_phone(self) -> None: class FormatHoursDisplayTests(SimpleTestCase): """Tests for the format_hours_display function.""" - def test_whole_hours(self): + def test_whole_hours(self) -> None: self.assertEqual(format_hours_display(2.0), "2h") - def test_hours_and_minutes(self): + def test_hours_and_minutes(self) -> None: self.assertEqual(format_hours_display(2.5), "2h 30m") - def test_minutes_only(self): + def test_minutes_only(self) -> None: self.assertEqual(format_hours_display(0.25), "15m") - def test_zero(self): + def test_zero(self) -> None: self.assertEqual(format_hours_display(0.0), "0h") - def test_none(self): + def test_none(self) -> None: self.assertEqual(format_hours_display(None), "0h") - def test_large_value(self): + def test_large_value(self) -> None: self.assertEqual(format_hours_display(10.75), "10h 45m") - def test_rounding(self): + def test_rounding(self) -> None: # 1.33 hours = 79.8 minutes, rounds to 80 = 1h 20m self.assertEqual(format_hours_display(1.33), "1h 20m") - def test_integer_input(self): + def test_integer_input(self) -> None: self.assertEqual(format_hours_display(3), "3h") @@ -134,25 +134,25 @@ class ConvertHtmlToReportlabTests(SimpleTestCase): # Basic paragraph handling - the core fix for preserving newlines # ------------------------------------------------------------------------- - def test_simple_paragraphs_preserve_line_breaks(self): + def test_simple_paragraphs_preserve_line_breaks(self) -> None: """Each

tag should create a line break in the output.""" html = "

Line 1

Line 2

Line 3

" result = convert_html_to_reportlab(html) self.assertEqual(result, "Line 1
Line 2
Line 3") - def test_blank_line_creates_paragraph_spacing(self): + def test_blank_line_creates_paragraph_spacing(self) -> None: """Quill's blank line (


) should create double line break.""" html = "

Section 1


Section 2

" result = convert_html_to_reportlab(html) self.assertEqual(result, "Section 1

Section 2") - def test_multiple_blank_lines_collapse_to_two(self): + def test_multiple_blank_lines_collapse_to_two(self) -> None: """Multiple consecutive blank lines should collapse to max 2 breaks.""" html = "

Section 1




Section 2

" result = convert_html_to_reportlab(html) self.assertEqual(result, "Section 1

Section 2") - def test_trailing_blank_lines_stripped(self): + def test_trailing_blank_lines_stripped(self) -> None: """Trailing blank lines should be removed.""" html = "

Content



" result = convert_html_to_reportlab(html) @@ -162,7 +162,7 @@ def test_trailing_blank_lines_stripped(self): # Real job notes from database - Job 96577 (RACK AND TABLE) # ------------------------------------------------------------------------- - def test_real_job_96577_rack_and_table(self): + def test_real_job_96577_rack_and_table(self) -> None: """ Real job notes with bold headers and blank line section separator. Should preserve structure with line breaks between items. @@ -196,7 +196,7 @@ def test_real_job_96577_rack_and_table(self): # Real job notes - Job 96573 (Kitchen wall structural) # ------------------------------------------------------------------------- - def test_real_job_96573_structural_with_inline_bold(self): + def test_real_job_96573_structural_with_inline_bold(self) -> None: """ Real job with inline bold text within a paragraph. Tests that partial bold formatting is preserved correctly. @@ -215,7 +215,7 @@ def test_real_job_96573_structural_with_inline_bold(self): # Real job notes - Job 96567 (with underline) # ------------------------------------------------------------------------- - def test_real_job_96567_with_underline(self): + def test_real_job_96567_with_underline(self) -> None: """ Real job with underlined section headers. Tests that tags are preserved. @@ -244,7 +244,7 @@ def test_real_job_96567_with_underline(self): # Real job notes - Job 96576 (with styled spans) # ------------------------------------------------------------------------- - def test_real_job_96576_strips_style_attributes(self): + def test_real_job_96576_strips_style_attributes(self) -> None: """ Real job with Quill's inline style spans. Style attributes should be stripped, content preserved. @@ -269,21 +269,21 @@ def test_real_job_96576_strips_style_attributes(self): # Edge cases # ------------------------------------------------------------------------- - def test_empty_string_returns_na(self): + def test_empty_string_returns_na(self) -> None: """Empty string should return N/A.""" self.assertEqual(convert_html_to_reportlab(""), "N/A") - def test_none_returns_na(self): + def test_none_returns_na(self) -> None: """None should return N/A.""" self.assertEqual(convert_html_to_reportlab(None), "N/A") - def test_plain_text_without_tags(self): + def test_plain_text_without_tags(self) -> None: """Plain text without HTML tags should pass through unchanged.""" text = "Plain text without any HTML" result = convert_html_to_reportlab(text) self.assertEqual(result, text) - def test_whitespace_only_returns_na(self): + def test_whitespace_only_returns_na(self) -> None: """Whitespace-only content should return N/A.""" self.assertEqual(convert_html_to_reportlab(" \n\t "), "N/A") @@ -291,28 +291,28 @@ def test_whitespace_only_returns_na(self): # Formatting tag conversions # ------------------------------------------------------------------------- - def test_strong_converts_to_b(self): + def test_strong_converts_to_b(self) -> None: """ should convert to .""" html = "

Bold text

" result = convert_html_to_reportlab(html) self.assertIn("Bold text", result) self.assertNotIn("", result) - def test_em_converts_to_i(self): + def test_em_converts_to_i(self) -> None: """ should convert to .""" html = "

Italic text

" result = convert_html_to_reportlab(html) self.assertIn("Italic text", result) self.assertNotIn("", result) - def test_s_converts_to_strike(self): + def test_s_converts_to_strike(self) -> None: """ (strikethrough) should convert to .""" html = "

Struck text

" result = convert_html_to_reportlab(html) self.assertIn("Struck text", result) self.assertNotIn("", result) - def test_anchor_converts_to_link(self): + def test_anchor_converts_to_link(self) -> None: """ should convert to .""" html = '

Click here

' result = convert_html_to_reportlab(html) @@ -323,7 +323,7 @@ def test_anchor_converts_to_link(self): # List handling # ------------------------------------------------------------------------- - def test_ordered_list_converts_to_numbered(self): + def test_ordered_list_converts_to_numbered(self) -> None: """
    should convert to numbered list with line breaks.""" html = "
    1. First
    2. Second
    3. Third
    " result = convert_html_to_reportlab(html) @@ -331,7 +331,7 @@ def test_ordered_list_converts_to_numbered(self): self.assertIn("2. Second", result) self.assertIn("3. Third", result) - def test_unordered_list_converts_to_bullets(self): + def test_unordered_list_converts_to_bullets(self) -> None: """
      should convert to bullet list with line breaks.""" html = "
      • Apple
      • Banana
      " result = convert_html_to_reportlab(html) @@ -342,13 +342,13 @@ def test_unordered_list_converts_to_bullets(self): # Heading handling # ------------------------------------------------------------------------- - def test_h1_converts_to_font_size_bold(self): + def test_h1_converts_to_font_size_bold(self) -> None: """

      should convert to large bold text.""" html = "

      Main Heading

      " result = convert_html_to_reportlab(html) self.assertIn('Main Heading', result) - def test_h2_converts_to_font_size_bold(self): + def test_h2_converts_to_font_size_bold(self) -> None: """

      should convert to bold text with appropriate size.""" html = "

      Sub Heading

      " result = convert_html_to_reportlab(html) @@ -358,19 +358,19 @@ def test_h2_converts_to_font_size_bold(self): # Special elements # ------------------------------------------------------------------------- - def test_blockquote_converts_to_italic(self): + def test_blockquote_converts_to_italic(self) -> None: """
      should convert to italic.""" html = "
      Quoted text
      " result = convert_html_to_reportlab(html) self.assertIn("Quoted text", result) - def test_pre_converts_to_courier_font(self): + def test_pre_converts_to_courier_font(self) -> None: """
       should convert to Courier font."""
               html = "
      Code block
      " result = convert_html_to_reportlab(html) self.assertIn('Code block', result) - def test_br_tags_preserved(self): + def test_br_tags_preserved(self) -> None: """
      tags should be preserved as
      .""" html = "

      Line one
      Line two

      " result = convert_html_to_reportlab(html) @@ -380,7 +380,7 @@ def test_br_tags_preserved(self): # Quill UI element removal # ------------------------------------------------------------------------- - def test_quill_ui_spans_removed(self): + def test_quill_ui_spans_removed(self) -> None: """Quill UI elements (class='ql-ui') should be completely removed.""" html = '

      TextUI elementMore text

      ' result = convert_html_to_reportlab(html) diff --git a/apps/job/urls.py b/apps/job/urls.py index 346a7dd85..06bcf0133 100644 --- a/apps/job/urls.py +++ b/apps/job/urls.py @@ -86,6 +86,11 @@ kanban_view_api.FetchJobsByColumnAPIView.as_view(), name="api_fetch_jobs_by_column", ), + path( + "jobs/kanban-changes/", + kanban_view_api.KanbanChangesAPIView.as_view(), + name="api_kanban_changes", + ), path( "jobs/status-values/", kanban_view_api.FetchStatusValuesAPIView.as_view(), diff --git a/apps/job/urls_rest.py b/apps/job/urls_rest.py index b2a7a4e26..454d40e12 100644 --- a/apps/job/urls_rest.py +++ b/apps/job/urls_rest.py @@ -37,6 +37,7 @@ JobDetailRestView, JobEventListRestView, JobEventRestView, + JobFinishRestView, JobHeaderRestView, JobInvoicesRestView, JobQuoteAcceptRestView, @@ -176,6 +177,12 @@ JobCostSummaryRestView.as_view(), name="job_cost_summary_rest", ), + # Finish Job workspace: customer balance + completion checklist + path( + "jobs//finish/", + JobFinishRestView.as_view(), + name="job_finish_rest", + ), # Job status choices path( "jobs/status-choices/", diff --git a/apps/job/views/__init__.py b/apps/job/views/__init__.py index 2fbcbf9a7..258a7c582 100644 --- a/apps/job/views/__init__.py +++ b/apps/job/views/__init__.py @@ -44,6 +44,7 @@ JobDetailRestView, JobEventListRestView, JobEventRestView, + JobFinishRestView, JobHeaderRestView, JobInvoicesRestView, JobQuoteAcceptRestView, @@ -92,6 +93,7 @@ FetchJobsAPIView, FetchJobsByColumnAPIView, FetchStatusValuesAPIView, + KanbanChangesAPIView, ReorderJobAPIView, UpdateJobStatusAPIView, ) @@ -136,6 +138,7 @@ "JobFileDetailView", "JobFileThumbnailView", "JobFilesCollectionView", + "JobFinishRestView", "JobHeaderRestView", "JobInvoicesRestView", "JobLabourRatesView", @@ -150,6 +153,7 @@ "JobSummaryRestView", "JobTimelineRestView", "JobUndoChangeRestView", + "KanbanChangesAPIView", "LabourSubtypeListView", "LabourSubtypeManageDetailView", "LabourSubtypeManageListCreateView", diff --git a/apps/job/views/job_rest_views.py b/apps/job/views/job_rest_views.py index 75c9f5f86..bc96dbb28 100644 --- a/apps/job/views/job_rest_views.py +++ b/apps/job/views/job_rest_views.py @@ -14,25 +14,31 @@ from uuid import UUID from django.core.cache import cache -from django.db import IntegrityError -from django.http import Http404, JsonResponse +from django.http import Http404, HttpRequest, JsonResponse from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from drf_spectacular.utils import OpenApiParameter, OpenApiTypes, extend_schema from rest_framework import status from rest_framework.decorators import api_view, permission_classes -from rest_framework.exceptions import NotFound from rest_framework.permissions import SAFE_METHODS, IsAuthenticated +from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView +from apps.accounting.services.finish_job_summary import build_finish_job_summary +from apps.accounting.services.invoice_calculation import ( + get_job_for_invoice_calculation, +) from apps.accounts.models import Staff +from apps.job.etag import generate_job_etag from apps.job.models import Job, JobDeltaRejection from apps.job.permissions import IsOfficeStaff from apps.job.serializers.job_serializer import ( CompanyDefaultsJobDetailSerializer, + FinishJobPayload, JobBasicInformationResponseSerializer, + JobCompletionChecklistUpdateSerializer, JobCostSummaryResponseSerializer, JobCreateResponseSerializer, JobCreateSerializer, @@ -43,6 +49,7 @@ JobEventCreateResponseSerializer, JobEventCreateSerializer, JobEventsResponseSerializer, + JobFinishResponseSerializer, JobHeaderResponseSerializer, JobInvoicesResponseSerializer, JobQuoteAcceptanceSerializer, @@ -57,10 +64,13 @@ from apps.job.services.job_rest_service import ( DeltaValidationError, JobRestService, - PreconditionFailed, ) +from apps.job.services.job_service import update_completion_checklist +from apps.workflow.etag import if_none_match_satisfied +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.models import CompanyDefaults from apps.workflow.services.error_persistence import persist_app_error +from apps.workflow.services.http_error_service import http_status_for_exception from apps.workflow.utils import parse_pagination_params logger = logging.getLogger(__name__) @@ -114,42 +124,24 @@ def handle_service_error(self, error: Exception) -> Response: error_message = str(error) - # PreconditionFailed covers DeltaValidationError, which subclasses it. - if isinstance(error, PreconditionFailed): - # ETag mismatch -> Optimistic concurrency conflict - error_response = { - "error": "Precondition failed (ETag mismatch). Reload the job and retry." - } - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response( - error_serializer.data, status=status.HTTP_412_PRECONDITION_FAILED + response_status = http_status_for_exception(error) + if isinstance(error, PreconditionFailedError): + response_message = ( + "Precondition failed (ETag mismatch). Reload the job and retry." ) - if isinstance(error, (Http404, NotFound)): - error_response = {"error": "Resource not found"} - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response(error_serializer.data, status=status.HTTP_404_NOT_FOUND) - if isinstance(error, IntegrityError): - # Handle database constraint violations (duplicates) - error_response = { - "error": "Duplicate event prevented by database constraint" + else: + response_message = error_message + + if response_status == status.HTTP_500_INTERNAL_SERVER_ERROR: + logger.error("Unhandled error: %s", error) + + error_serializer = JobRestErrorResponseSerializer( + { + "error": response_message, + "details": {"error_id": app_error.id}, } - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response(error_serializer.data, status=status.HTTP_409_CONFLICT) - if isinstance(error, PermissionError): - error_response = {"error": error_message} - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response(error_serializer.data, status=status.HTTP_403_FORBIDDEN) - if isinstance(error, ValueError): - error_response = {"error": error_message} - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response(error_serializer.data, status=status.HTTP_400_BAD_REQUEST) - - logger.exception(f"Unhandled error: {error}") - error_response = {"error": "Internal server error"} - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response( - error_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) + return Response(error_serializer.data, status=response_status) def check_request_debounce( self, request, operation_key: str, debounce_seconds: int = 2 @@ -177,38 +169,19 @@ def check_request_debounce( return False # Allow - outside debounce period # === Optimistic Concurrency (ETag) helpers === - def _normalize_etag(self, etag: str | None) -> str | None: - """Normalize an ETag/If-Match/If-None-Match value for comparison.""" - if not etag: - return None - val = etag.strip() - if val.startswith("W/"): - val = val[2:].strip() - if len(val) >= 2 and ( - (val[0] == '"' and val[-1] == '"') or (val[0] == "'" and val[-1] == "'") - ): - val = val[1:-1] - return val - def _gen_job_etag(self, job: Job) -> str: - """Generate a weak ETag for a Job based on its last update timestamp.""" - try: - ts_ms = int(job.updated_at.timestamp() * 1000) - except Exception: - ts_ms = 0 - return f'W/"job:{job.id}:{ts_ms}"' - - def _get_if_match(self, request) -> str | None: - """Extract normalized If-Match header value.""" - header = request.headers.get("If-Match") or request.META.get("HTTP_IF_MATCH") - return self._normalize_etag(header) if header else None - - def _get_if_none_match(self, request) -> str | None: - """Extract normalized If-None-Match header value.""" - header = request.headers.get("If-None-Match") or request.META.get( + """Generate a strong ETag for a Job based on its last update timestamp.""" + return generate_job_etag(job) + + def _get_if_match(self, request: HttpRequest) -> str | None: + """Extract the raw If-Match header for service-layer comparison.""" + return request.headers.get("If-Match") or request.META.get("HTTP_IF_MATCH") + + def _get_if_none_match(self, request: HttpRequest) -> str | None: + """Extract the raw If-None-Match header.""" + return request.headers.get("If-None-Match") or request.META.get( "HTTP_IF_NONE_MATCH" ) - return self._normalize_etag(header) if header else None def _precondition_required_response(self) -> Response: """Return 428 when If-Match is required but missing.""" @@ -351,7 +324,7 @@ def get(self, request, job_id): if ( if_none_match and current_etag - and self._normalize_etag(current_etag) == if_none_match + and if_none_match_satisfied(if_none_match, current_etag) ): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -604,7 +577,7 @@ def get(self, request, job_id): if ( if_none_match and current_etag - and self._normalize_etag(current_etag) == if_none_match + and if_none_match_satisfied(if_none_match, current_etag) ): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -663,6 +636,10 @@ def post(self, request, job_id): } """ try: + if_match = self._get_if_match(request) + if not if_match: + return self._precondition_required_response() + # Debounce check - prevent rapid requests debounce_key = f"add_event:{job_id}" if self.check_request_debounce(request, debounce_key, debounce_seconds=2): @@ -706,33 +683,10 @@ def post(self, request, job_id): error_serializer = JobRestErrorResponseSerializer(error_response) return Response(error_serializer.data, status=status.HTTP_409_CONFLICT) - # Require If-Match for optimistic concurrency control on event creation - if_match = self._get_if_match(request) - if not if_match: - return self._precondition_required_response() - - # Verify company ETag against current job version - try: - job_for_etag = Job.objects.only("id", "updated_at").get(id=job_id) - current_etag_norm = self._normalize_etag( - self._gen_job_etag(job_for_etag) - ) - if current_etag_norm != if_match: - error_response = { - "error": "Precondition failed (ETag mismatch). Reload the job and retry." - } - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response( - error_serializer.data, - status=status.HTTP_412_PRECONDITION_FAILED, - ) - except Job.DoesNotExist: - error_response = {"error": "Resource not found"} - error_serializer = JobRestErrorResponseSerializer(error_response) - return Response(error_serializer.data, status=status.HTTP_404_NOT_FOUND) - # Create event via service - result = JobRestService.add_job_event(job_id, description, request.user) + result = JobRestService.add_job_event( + job_id, description, request.user, if_match + ) # Set duplicate prevention cache (5 minutes) cache.set(duplicate_check_key, True, 300) @@ -908,7 +862,7 @@ def get(self, request, job_id): # Conditional GET using ETag if_none_match = self._get_if_none_match(request) - if if_none_match and self._normalize_etag(current_etag) == if_none_match: + if if_none_match and if_none_match_satisfied(if_none_match, current_etag): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -918,8 +872,6 @@ def get(self, request, job_id): resp = self._set_etag(resp, current_etag) return resp - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc except Exception as e: return self.handle_service_error(e) @@ -950,7 +902,7 @@ def get(self, request, job_id): job = Job.objects.only("id", "updated_at").get(id=job_id) current_etag = self._gen_job_etag(job) inm = self._get_if_none_match(request) - if inm and self._normalize_etag(current_etag) == inm: + if inm and if_none_match_satisfied(inm, current_etag): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -960,8 +912,6 @@ def get(self, request, job_id): resp = Response(serializer.data, status=status.HTTP_200_OK) return self._set_etag(resp, current_etag) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc except Exception as e: return self.handle_service_error(e) @@ -992,7 +942,7 @@ def get(self, request, job_id): job = Job.objects.only("id", "updated_at").get(id=job_id) current_etag = self._gen_job_etag(job) inm = self._get_if_none_match(request) - if inm and self._normalize_etag(current_etag) == inm: + if inm and if_none_match_satisfied(inm, current_etag): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -1001,8 +951,6 @@ def get(self, request, job_id): resp = Response(quote, status=status.HTTP_200_OK) return self._set_etag(resp, current_etag) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc except Exception as e: return self.handle_service_error(e) @@ -1033,7 +981,7 @@ def get(self, request, job_id): job = Job.objects.only("id", "updated_at").get(id=job_id) current_etag = self._gen_job_etag(job) inm = self._get_if_none_match(request) - if inm and self._normalize_etag(current_etag) == inm: + if inm and if_none_match_satisfied(inm, current_etag): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) @@ -1127,8 +1075,80 @@ def calculate_profit_margin(cost_set): resp = Response(serializer.data, status=status.HTTP_200_OK) return self._set_etag(resp, current_etag) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc + except Exception as e: + return self.handle_service_error(e) + + +def _finish_job_payload(job: Job) -> FinishJobPayload: + return { + "summary": build_finish_job_summary(job), + "checklist": job, + } + + +@method_decorator(csrf_exempt, name="dispatch") +class JobFinishRestView(BaseJobRestView): + """Finish Job workspace: the authoritative customer balance and checklist. + + Deliberately not ETagged on the job. The balance moves when invoices change, + and an invoice arriving from Xero does not touch ``job.updated_at``, so a + job-derived ETag would answer 304 while showing a customer the wrong amount + to pay. + """ + + serializer_class = JobFinishResponseSerializer + + @extend_schema( + responses={ + 200: JobFinishResponseSerializer, + 400: JobRestErrorResponseSerializer, + }, + description=( + "Fetch the authoritative Finish Job customer balance and completion " + "checklist for a job. All currency values are calculated server-side." + ), + tags=["Jobs"], + ) + def get(self, request: Request, job_id: UUID) -> Response: + try: + job = get_job_for_invoice_calculation(job_id) + serializer = JobFinishResponseSerializer(_finish_job_payload(job)) + return Response(serializer.data, status=status.HTTP_200_OK) + + except Exception as e: + return self.handle_service_error(e) + + @extend_schema( + request=JobCompletionChecklistUpdateSerializer, + responses={ + 200: JobFinishResponseSerializer, + 400: JobRestErrorResponseSerializer, + }, + description=( + "Update one or more completion checklist items. Each changed item adds " + "a job-history event. Unknown item keys are rejected." + ), + tags=["Jobs"], + ) + def patch(self, request: Request, job_id: UUID) -> Response: + try: + # A tick records who made it, so narrow the authenticated user to a + # real Staff rather than casting (ADR 0028). + staff = request.user + if not isinstance(staff, Staff): + raise ValueError( + "Checklist updates require an authenticated staff member." + ) + + updates = JobCompletionChecklistUpdateSerializer(data=request.data) + updates.is_valid(raise_exception=True) + + job = update_completion_checklist( + get_job_for_invoice_calculation(job_id), updates.validated_data, staff + ) + serializer = JobFinishResponseSerializer(_finish_job_payload(job)) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: return self.handle_service_error(e) @@ -1196,8 +1216,6 @@ def get(self, request, job_id): serializer = JobEventsResponseSerializer({"events": events}) return Response(serializer.data, status=status.HTTP_200_OK) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc except Exception as e: return self.handle_service_error(e) @@ -1437,14 +1455,11 @@ def get(self, request, job_id): """ try: # Conditional GET using ETag based on Job.updated_at - try: - job = Job.objects.only("id", "updated_at").get(id=job_id) - current_etag = self._gen_job_etag(job) - except Job.DoesNotExist as exc: - raise ValueError(f"Job with id {job_id} not found") from exc + job = Job.objects.only("id", "updated_at").get(id=job_id) + current_etag = self._gen_job_etag(job) if_none_match = self._get_if_none_match(request) - if if_none_match and self._normalize_etag(current_etag) == if_none_match: + if if_none_match and if_none_match_satisfied(if_none_match, current_etag): resp = Response(status=status.HTTP_304_NOT_MODIFIED) return self._set_etag(resp, current_etag) diff --git a/apps/job/views/kanban_view_api.py b/apps/job/views/kanban_view_api.py index 9c1bb9a88..8b51e0bfc 100644 --- a/apps/job/views/kanban_view_api.py +++ b/apps/job/views/kanban_view_api.py @@ -23,6 +23,7 @@ FetchStatusValuesResponseSerializer, JobReorderSerializer, JobStatusUpdateSerializer, + KanbanChangesResponseSerializer, KanbanErrorResponseSerializer, KanbanSuccessResponseSerializer, ) @@ -538,3 +539,62 @@ def get(self, request: Request, column_id: str) -> Response: return Response( error_serializer.data, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) + + +class KanbanChangesAPIView(APIView): + """Return changed Kanban cards after an opaque Job dataset version.""" + + permission_classes = [IsAuthenticated] + serializer_class = KanbanChangesResponseSerializer + + @extend_schema( + operation_id="getKanbanChanges", + parameters=[ + OpenApiParameter( + name="after", + type=str, + location=OpenApiParameter.QUERY, + required=True, + description="Opaque previous kanban dataset version", + ) + ], + responses={ + 200: KanbanChangesResponseSerializer, + 400: KanbanErrorResponseSerializer, + }, + ) + def get(self, request: Request) -> Response: + after = request.query_params.get("after") + if after is None: + return Response( + {"success": False, "error": "after is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + changes = KanbanService.get_kanban_changes(after) + response_data = { + "success": True, + "jobs": KanbanService.serialize_jobs_for_api(changes.jobs), + "removed_job_ids": changes.removed_job_ids, + "full_refresh_required": changes.full_refresh_required, + } + serializer = KanbanChangesResponseSerializer(data=response_data) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) + except ValueError as exc: + app_error = persist_app_error(exc) + error_serializer = KanbanErrorResponseSerializer( + { + "success": False, + "error": str(exc), + "details": {"error_id": app_error.id}, + } + ) + return Response( + error_serializer.data, + status=status.HTTP_400_BAD_REQUEST, + ) + except Exception as exc: + _persist_unexpected_error(exc) + raise diff --git a/apps/operations/migrations/0002_forbid_blank_text.py b/apps/operations/migrations/0002_forbid_blank_text.py new file mode 100644 index 000000000..e026c80f6 --- /dev/null +++ b/apps/operations/migrations/0002_forbid_blank_text.py @@ -0,0 +1,46 @@ +"""Forbid empty strings in nullable text columns (operations). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "operations_jobprojection": [ + "unscheduled_reason" + ], + "operations_schedulerrun": [ + "failure_reason" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("operations", "0001_baseline"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/operations/services/scheduler_service.py b/apps/operations/services/scheduler_service.py index 3817cbc42..7573d910b 100644 --- a/apps/operations/services/scheduler_service.py +++ b/apps/operations/services/scheduler_service.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from datetime import date, timedelta from typing import Dict, List, Optional +from uuid import UUID from django.db import models, transaction from django.utils import timezone @@ -110,7 +111,7 @@ def _gather_workshop_staff(today: date) -> List[Staff]: def _classify_jobs( jobs: List[Job], staff_count: int, - assigned_staff_by_job_id: Dict[object, frozenset], + assigned_staff_by_job_id: Dict[UUID, frozenset[UUID]], ) -> tuple[List[JobScheduleState], List[UnschedulableJob]]: """ Separate jobs into schedulable and unschedulable. @@ -397,7 +398,7 @@ def run_workshop_schedule() -> SchedulerRun: ), ) ) - assigned_staff_by_job_id: Dict[object, frozenset] = { + assigned_staff_by_job_id: Dict[UUID, frozenset[UUID]] = { job.id: frozenset() for job in jobs } through_model = Job.people.through diff --git a/apps/operations/tests/test_scheduler_persistence.py b/apps/operations/tests/test_scheduler_persistence.py index 2626ac90e..3a9230a4c 100644 --- a/apps/operations/tests/test_scheduler_persistence.py +++ b/apps/operations/tests/test_scheduler_persistence.py @@ -73,7 +73,7 @@ def _make_job(company: Company, staff: Staff, name: str = "Persist Test Job") -> class TestSchedulerRunRecord(BaseTestCase): """Verify SchedulerRun records are created correctly.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Persist Company", xero_last_modified=timezone.now(), @@ -81,17 +81,17 @@ def setUp(self): _make_staff("p1") _make_job(self.client_obj, self.test_staff) - def test_successful_run_creates_scheduler_run_record(self): + def test_successful_run_creates_scheduler_run_record(self) -> None: """A successful run creates exactly one SchedulerRun record.""" run_workshop_schedule() self.assertEqual(SchedulerRun.objects.count(), 1) - def test_successful_run_creates_job_projections(self): + def test_successful_run_creates_job_projections(self) -> None: """A successful run creates JobProjection records.""" run = run_workshop_schedule() self.assertTrue(JobProjection.objects.filter(scheduler_run=run).exists()) - def test_successful_run_creates_allocation_blocks(self): + def test_successful_run_creates_allocation_blocks(self) -> None: """A successful run creates AllocationBlock records.""" run = run_workshop_schedule() self.assertTrue(AllocationBlock.objects.filter(scheduler_run=run).exists()) @@ -100,7 +100,7 @@ def test_successful_run_creates_allocation_blocks(self): class TestFailedRunPreservesData(BaseTestCase): """Verify a failed run does not overwrite good data from a previous run.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Persist Company", xero_last_modified=timezone.now(), @@ -108,7 +108,7 @@ def setUp(self): _make_staff("p2") _make_job(self.client_obj, self.test_staff) - def test_failed_run_preserves_last_successful(self): + def test_failed_run_preserves_last_successful(self) -> None: """After a failed run, the previously successful SchedulerRun still exists.""" # First successful run first_run = run_workshop_schedule() @@ -127,7 +127,7 @@ def test_failed_run_preserves_last_successful(self): # Original successful run still present self.assertTrue(SchedulerRun.objects.filter(id=first_run.id).exists()) - def test_failed_run_does_not_overwrite_good_data(self): + def test_failed_run_does_not_overwrite_good_data(self) -> None: """Old projections still exist after a failed run attempt.""" first_run = run_workshop_schedule() original_projection_count = JobProjection.objects.filter( @@ -154,14 +154,14 @@ def test_failed_run_does_not_overwrite_good_data(self): class TestLatestForecastReadsNewestRun(BaseTestCase): """Verify the API reads from the most recent successful SchedulerRun.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Persist Company", xero_last_modified=timezone.now(), ) _make_staff("p3") - def test_latest_forecast_from_latest_successful_run(self): + def test_latest_forecast_from_latest_successful_run(self) -> None: """Two successful runs exist; the API reads data from the newer run.""" job1 = _make_job(self.client_obj, self.test_staff, name="Job One") run_workshop_schedule() diff --git a/apps/operations/tests/test_scheduler_service.py b/apps/operations/tests/test_scheduler_service.py index b55ed8ba3..7d4e6863e 100644 --- a/apps/operations/tests/test_scheduler_service.py +++ b/apps/operations/tests/test_scheduler_service.py @@ -87,11 +87,11 @@ def _set_summary_hours(cost_set: CostSet, hours: float) -> None: class TestHoursComputation(BaseTestCase): """Tests for how remaining hours are computed from estimate/quote/actual.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() _make_staff("worker1") - def test_estimate_hours_used_when_present(self): + def test_estimate_hours_used_when_present(self) -> None: """Job with estimate hours schedules; remaining = estimate - actual at run time.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 10.0) @@ -104,7 +104,7 @@ def test_estimate_hours_used_when_present(self): # not the post-simulation residual. self.assertAlmostEqual(proj.remaining_hours, 10.0, delta=0.1) - def test_quote_fallback_when_estimate_zero(self): + def test_quote_fallback_when_estimate_zero(self) -> None: """Job with zero estimate but valid quote schedules using quote hours.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 0.0) @@ -116,7 +116,7 @@ def test_quote_fallback_when_estimate_zero(self): self.assertFalse(proj.is_unscheduled) self.assertAlmostEqual(proj.remaining_hours, 8.0, delta=0.1) - def test_non_workshop_estimate_hours_are_ignored(self): + def test_non_workshop_estimate_hours_are_ignored(self) -> None: """Office-like estimate lines do not create schedulable work.""" job = _make_job(self.client_obj, self.test_staff) admin = LabourSubtype.objects.get(name="Admin") @@ -140,7 +140,7 @@ def test_non_workshop_estimate_hours_are_ignored(self): UnscheduledReason.MISSING_ESTIMATE_OR_QUOTE_HOURS, ) - def test_onsite_estimate_hours_are_scheduled(self): + def test_onsite_estimate_hours_are_scheduled(self) -> None: """Onsite work consumes the same schedulable staff capacity as workshop work.""" job = _make_job(self.client_obj, self.test_staff) onsite = LabourSubtype.objects.get(name="Onsite") @@ -161,7 +161,7 @@ def test_onsite_estimate_hours_are_scheduled(self): self.assertFalse(proj.is_unscheduled) self.assertAlmostEqual(proj.remaining_hours, 6.0, delta=0.1) - def test_remaining_hours_is_at_run_time_not_post_simulation(self): + def test_remaining_hours_is_at_run_time_not_post_simulation(self) -> None: """Multi-day jobs persist the at-run-time remaining hours, not the post-simulation residual. The frontend needs to display 'X hours of work left' — once the simulation has consumed the work, that value is 0, @@ -176,7 +176,7 @@ def test_remaining_hours_is_at_run_time_not_post_simulation(self): self.assertFalse(proj.is_unscheduled) self.assertAlmostEqual(proj.remaining_hours, 24.0, delta=0.1) - def test_no_hours_unscheduled(self): + def test_no_hours_unscheduled(self) -> None: """Job with no hours in estimate or quote is unscheduled.""" job = _make_job(self.client_obj, self.test_staff) # Both estimate and quote have 0 hours by default @@ -190,7 +190,7 @@ def test_no_hours_unscheduled(self): UnscheduledReason.MISSING_ESTIMATE_OR_QUOTE_HOURS, ) - def test_zero_remaining_hours_excluded(self): + def test_zero_remaining_hours_excluded(self) -> None: """Job where actual >= estimate is unscheduled (not actively allocated).""" from apps.workflow.models import XeroPayItem @@ -226,7 +226,7 @@ def test_zero_remaining_hours_excluded(self): proj = JobProjection.objects.get(scheduler_run=run, job=job) self.assertTrue(proj.is_unscheduled) - def test_non_workshop_actual_time_does_not_reduce_remaining_hours(self): + def test_non_workshop_actual_time_does_not_reduce_remaining_hours(self) -> None: """Admin actual time does not consume workshop scheduled hours.""" from apps.workflow.models import XeroPayItem @@ -266,10 +266,10 @@ def test_non_workshop_actual_time_does_not_reduce_remaining_hours(self): class TestStaffFiltering(BaseTestCase): """Tests for which staff participate in scheduling.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() - def test_only_workshop_staff_contribute(self): + def test_only_workshop_staff_contribute(self) -> None: """Non-workshop staff are excluded from allocation.""" office_staff = _make_staff("office", is_workshop=False) workshop_staff = _make_staff("shop") @@ -286,7 +286,7 @@ def test_only_workshop_staff_contribute(self): self.assertNotIn(office_staff.id, staff_ids) self.assertIn(workshop_staff.id, staff_ids) - def test_impossible_staffing_unscheduled(self): + def test_impossible_staffing_unscheduled(self) -> None: """min_people > available staff count causes unscheduled with correct reason.""" _make_staff("solo") # only 1 workshop staff @@ -306,10 +306,10 @@ def test_impossible_staffing_unscheduled(self): class TestPeopleAssignment(BaseTestCase): """Tests for min/max people constraints during allocation.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() - def test_one_person_default(self): + def test_one_person_default(self) -> None: """Job with min=1, max=1 gets exactly one worker per day.""" _make_staff("w1") _make_staff("w2") @@ -330,7 +330,7 @@ def test_one_person_default(self): for day in per_day: self.assertEqual(day["count"], 1) - def test_multi_person_job(self): + def test_multi_person_job(self) -> None: """Job with min_people=2 gets at least 2 workers when enough staff available.""" _make_staff("w1") _make_staff("w2") @@ -359,10 +359,10 @@ def test_multi_person_job(self): class TestAssignedStaffPreference(BaseTestCase): """Tests that explicitly-assigned staff are preferred when available.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() - def test_assigned_available_staff_chosen_over_others(self): + def test_assigned_available_staff_chosen_over_others(self) -> None: """When a job has assigned staff and they're available, they get the work.""" # Three workers — without assignment, the scheduler picks by capacity # which would tie and fall back to insertion order. @@ -381,7 +381,7 @@ def test_assigned_available_staff_chosen_over_others(self): staff_ids = set(blocks.values_list("staff_id", flat=True)) self.assertEqual(staff_ids, {w1.id}) - def test_assigned_staff_preferred_even_with_lower_capacity(self): + def test_assigned_staff_preferred_even_with_lower_capacity(self) -> None: """Assigned worker is picked over a non-assigned worker with more remaining capacity (capacity sort would otherwise win).""" # Big-capacity worker is unassigned; small-capacity worker is assigned. @@ -400,7 +400,7 @@ def test_assigned_staff_preferred_even_with_lower_capacity(self): self.assertIn(small.id, staff_ids) self.assertNotIn(big.id, staff_ids) - def test_remaining_slots_filled_from_unassigned_pool(self): + def test_remaining_slots_filled_from_unassigned_pool(self) -> None: """If max_people exceeds the number of assigned staff, remaining slots are filled from the unassigned pool.""" w1 = _make_staff("w1") @@ -424,7 +424,7 @@ def test_remaining_slots_filled_from_unassigned_pool(self): # Second slot must be one of the others self.assertTrue({w2.id, w3.id} & first_day_staff) - def test_unavailable_assigned_staff_falls_back_to_others(self): + def test_unavailable_assigned_staff_falls_back_to_others(self) -> None: """If the assigned worker has no capacity today, the scheduler still picks an available worker rather than skipping the job.""" # Assigned worker doesn't work any day (no scheduled hours) @@ -460,7 +460,7 @@ class TestBookedTimeReducesCapacity(BaseTestCase): """Tests that pre-existing time CostLines (worked time, leave) reduce a staff member's available capacity for the day.""" - def setUp(self): + def setUp(self) -> None: from apps.workflow.models.company_defaults import CompanyDefaults self.client_obj = _make_client() @@ -505,7 +505,7 @@ def _book_time(self, staff, on_date, hours, cost_set): }, ) - def test_full_day_leave_blocks_allocation(self): + def test_full_day_leave_blocks_allocation(self) -> None: """A full-day leave entry on day D removes that worker from day D.""" on_leave = _make_staff("on_leave") backup = _make_staff("backup") @@ -532,7 +532,7 @@ def test_full_day_leave_blocks_allocation(self): self.assertNotIn(on_leave.id, staff_ids) self.assertIn(backup.id, staff_ids) - def test_partial_day_booking_reduces_capacity(self): + def test_partial_day_booking_reduces_capacity(self) -> None: """Pre-booking 4h leaves only the remaining 4h available that day.""" worker = _make_staff("part") leave_job = self._make_special_leave_job() @@ -561,7 +561,7 @@ def test_partial_day_booking_reduces_capacity(self): # Capacity reduced from 8h to 4h self.assertAlmostEqual(first_day_total, 4.0, delta=0.01) - def test_estimate_or_quote_costlines_do_not_reduce_capacity(self): + def test_estimate_or_quote_costlines_do_not_reduce_capacity(self) -> None: """CostLines on estimate/quote CostSets are hypothetical and must not reduce real capacity.""" worker = _make_staff("solo") @@ -615,7 +615,7 @@ class TestStartDateOnlyOnRealWork(BaseTestCase): low-priority jobs whose chosen workers had drained capacity got start_date=today + zero AllocationBlocks.""" - def setUp(self): + def setUp(self) -> None: from apps.workflow.models.company_defaults import CompanyDefaults self.client_obj = _make_client() @@ -623,7 +623,7 @@ def setUp(self): cd.workshop_efficiency_factor = Decimal("1.000") cd.save() - def test_low_priority_job_does_not_start_on_capacity_starved_day(self): + def test_low_priority_job_does_not_start_on_capacity_starved_day(self) -> None: """One worker, two jobs needing one person each. High-priority job consumes the day's capacity. Low-priority job must NOT have its start_date set to the same day — it can't have started yet.""" @@ -657,7 +657,7 @@ def test_low_priority_job_does_not_start_on_capacity_starved_day(self): ) self.assertTrue(first_blocks.exists()) - def test_unreachable_job_marked_unscheduled(self): + def test_unreachable_job_marked_unscheduled(self) -> None: """A schedulable job whose simulation never lands any work in the horizon is recorded as unscheduled with NOT_REACHED_IN_HORIZON.""" # One worker, one always-busy high-priority job that never finishes, @@ -693,7 +693,7 @@ class TestEfficiencyFactor(BaseTestCase): """Tests that CompanyDefaults.workshop_efficiency_factor scales daily schedulable capacity.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() def _set_efficiency(self, value): @@ -703,7 +703,7 @@ def _set_efficiency(self, value): cd.workshop_efficiency_factor = Decimal(str(value)) cd.save() - def test_factor_scales_daily_allocation(self): + def test_factor_scales_daily_allocation(self) -> None: """At 0.75, an 8h-shift worker delivers 6h on day one of an 8h job.""" worker = _make_staff("w") self._set_efficiency("0.750") @@ -727,7 +727,7 @@ def test_factor_scales_daily_allocation(self): ) self.assertAlmostEqual(first_day_total, 6.0, delta=0.01) - def test_factor_one_means_full_capacity(self): + def test_factor_one_means_full_capacity(self) -> None: """Setting efficiency to 1.0 restores nominal clock-hour capacity.""" worker = _make_staff("w") self._set_efficiency("1.000") @@ -755,11 +755,11 @@ def test_factor_one_means_full_capacity(self): class TestAllocationBlocks(BaseTestCase): """Tests for AllocationBlock creation and content.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() _make_staff("worker1") - def test_anticipated_staff_populated(self): + def test_anticipated_staff_populated(self) -> None: """Scheduled job has AllocationBlock records linking staff to the job.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 8.0) @@ -769,7 +769,7 @@ def test_anticipated_staff_populated(self): blocks = AllocationBlock.objects.filter(scheduler_run=run, job=job) self.assertTrue(blocks.exists()) - def test_allocation_blocks_persisted(self): + def test_allocation_blocks_persisted(self) -> None: """AllocationBlock records exist after a scheduler run.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 8.0) @@ -782,11 +782,11 @@ def test_allocation_blocks_persisted(self): class TestDateCalculations(BaseTestCase): """Tests for anticipated start/end date assignment.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() _make_staff("worker1") - def test_today_uses_local_timezone_not_utc(self): + def test_today_uses_local_timezone_not_utc(self) -> None: """The scheduler must anchor 'today' on the project's local timezone (Pacific/Auckland). Using timezone.now().date() returns the UTC date, so for half the day in NZ the schedule shows yesterday as today.""" @@ -803,7 +803,7 @@ def test_today_uses_local_timezone_not_utc(self): self.assertIsNotNone(earliest_block) self.assertGreaterEqual(earliest_block.allocation_date, timezone.localdate()) - def test_start_date_is_first_allocation_day(self): + def test_start_date_is_first_allocation_day(self) -> None: """anticipated_start_date equals the earliest AllocationBlock date.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 8.0) @@ -818,7 +818,7 @@ def test_start_date_is_first_allocation_day(self): ) self.assertEqual(proj.anticipated_start_date, earliest_block.allocation_date) - def test_end_date_is_last_allocation_day(self): + def test_end_date_is_last_allocation_day(self) -> None: """anticipated_end_date equals the latest AllocationBlock date.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 8.0) @@ -833,7 +833,7 @@ def test_end_date_is_last_allocation_day(self): ) self.assertEqual(proj.anticipated_end_date, latest_block.allocation_date) - def test_multi_day_job_carries_over(self): + def test_multi_day_job_carries_over(self) -> None: """Job with more hours than one day spans multiple working days.""" job = _make_job(self.client_obj, self.test_staff) # 1 worker @ 8h/day, 24h of work needs at least 3 days @@ -845,7 +845,7 @@ def test_multi_day_job_carries_over(self): self.assertFalse(proj.is_unscheduled) self.assertGreater(proj.anticipated_end_date, proj.anticipated_start_date) - def test_scheduled_job_has_both_dates(self): + def test_scheduled_job_has_both_dates(self) -> None: """Every scheduled projection has non-null start and end dates.""" job = _make_job(self.client_obj, self.test_staff) _set_summary_hours(job.latest_estimate, 8.0) @@ -862,10 +862,10 @@ def test_scheduled_job_has_both_dates(self): class TestPriorityAndCapacity(BaseTestCase): """Tests for priority ordering and capacity consumption.""" - def setUp(self): + def setUp(self) -> None: self.client_obj = _make_client() - def test_priority_order_affects_scheduling(self): + def test_priority_order_affects_scheduling(self) -> None: """Higher priority job starts no later than lower priority job.""" _make_staff("w1") diff --git a/apps/operations/tests/test_workshop_schedule_api.py b/apps/operations/tests/test_workshop_schedule_api.py index f6c82eb85..5aba59aaf 100644 --- a/apps/operations/tests/test_workshop_schedule_api.py +++ b/apps/operations/tests/test_workshop_schedule_api.py @@ -78,7 +78,7 @@ def _make_job( class WorkshopScheduleGetTests(BaseAPITestCase): """Tests for GET /api/operations/workshop-schedule/""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="API Test Company", xero_last_modified=timezone.now(), @@ -88,7 +88,7 @@ def setUp(self): self.api_client.force_authenticate(user=self.staff) self.url = reverse("operations:workshop-schedule") - def test_no_run_returns_empty_response(self): + def test_no_run_returns_empty_response(self) -> None: """GET with no SchedulerRun returns empty lists.""" response = self.api_client.get(self.url) self.assertEqual(response.status_code, 200) @@ -97,7 +97,7 @@ def test_no_run_returns_empty_response(self): self.assertEqual(data["jobs"], []) self.assertEqual(data["unscheduled_jobs"], []) - def test_get_returns_expected_shape(self): + def test_get_returns_expected_shape(self) -> None: """GET returns 200 with days, jobs, and unscheduled_jobs keys.""" _make_job(self.client_obj, self.test_staff) run_workshop_schedule() @@ -109,7 +109,7 @@ def test_get_returns_expected_shape(self): self.assertIn("jobs", data) self.assertIn("unscheduled_jobs", data) - def test_scheduled_job_has_dates(self): + def test_scheduled_job_has_dates(self) -> None: """Scheduled jobs include anticipated_start_date and anticipated_end_date.""" _make_job(self.client_obj, self.test_staff) run_workshop_schedule() @@ -122,7 +122,7 @@ def test_scheduled_job_has_dates(self): self.assertIn("anticipated_start_date", job) self.assertIn("anticipated_end_date", job) - def test_scheduled_job_has_people_fields(self): + def test_scheduled_job_has_people_fields(self) -> None: """Scheduled jobs include min_people, max_people, and assigned_staff.""" _make_job(self.client_obj, self.test_staff) run_workshop_schedule() @@ -135,7 +135,7 @@ def test_scheduled_job_has_people_fields(self): self.assertIn("max_people", job) self.assertIn("assigned_staff", job) - def test_unscheduled_job_has_reason(self): + def test_unscheduled_job_has_reason(self) -> None: """Unscheduled jobs include a machine-readable reason field.""" # Job with no hours → unscheduled Job.objects.create( @@ -156,7 +156,7 @@ def test_unscheduled_job_has_reason(self): self.assertIn("reason", unscheduled) self.assertTrue(len(unscheduled["reason"]) > 0) - def test_invalid_day_horizon_returns_400(self): + def test_invalid_day_horizon_returns_400(self) -> None: """GET with day_horizon=0 returns 400.""" response = self.api_client.get(self.url, {"day_horizon": 0}) self.assertEqual(response.status_code, 400) @@ -165,7 +165,7 @@ def test_invalid_day_horizon_returns_400(self): class WorkshopScheduleRecalculateTests(BaseAPITestCase): """Tests for POST /api/operations/workshop-schedule/recalculate/""" - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Recalc Test Company", xero_last_modified=timezone.now(), @@ -176,7 +176,7 @@ def setUp(self): self.url = reverse("operations:workshop-schedule-recalculate") self.get_url = reverse("operations:workshop-schedule") - def test_post_recalculate_returns_same_shape(self): + def test_post_recalculate_returns_same_shape(self) -> None: """POST to recalculate returns 200 with days, jobs, unscheduled_jobs.""" _make_job(self.client_obj, self.test_staff) @@ -187,7 +187,7 @@ def test_post_recalculate_returns_same_shape(self): self.assertIn("jobs", data) self.assertIn("unscheduled_jobs", data) - def test_post_recalculate_supports_quote_fallback_hours(self): + def test_post_recalculate_supports_quote_fallback_hours(self) -> None: """Jobs with no estimate hours still schedule from quote hours.""" job = _make_job(self.client_obj, self.test_staff, hours=0.0) _set_workshop_hours(job.latest_quote, 8.0) diff --git a/apps/process/management/commands/import_dropbox_hs_documents.py b/apps/process/management/commands/import_dropbox_hs_documents.py index a3f9831db..3c9166911 100644 --- a/apps/process/management/commands/import_dropbox_hs_documents.py +++ b/apps/process/management/commands/import_dropbox_hs_documents.py @@ -761,7 +761,7 @@ def handle(self, *args, **options): status="active", document_number=doc_number, title=title, - site_location="", + site_location=None, google_doc_id=google_doc_id, google_doc_url=google_doc_url, ) diff --git a/apps/process/migrations/0002_forbid_blank_text.py b/apps/process/migrations/0002_forbid_blank_text.py new file mode 100644 index 000000000..0b0768d3c --- /dev/null +++ b/apps/process/migrations/0002_forbid_blank_text.py @@ -0,0 +1,46 @@ +"""Forbid empty strings in nullable text columns (process). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "process_form": [ + "document_number" + ], + "process_procedure": [ + "document_number" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("process", "0001_baseline"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/process/migrations/0003_text_unset_is_null.py b/apps/process/migrations/0003_text_unset_is_null.py new file mode 100644 index 000000000..227a62f18 --- /dev/null +++ b/apps/process/migrations/0003_text_unset_is_null.py @@ -0,0 +1,87 @@ +"""Give process's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("process", "0002_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="historicalprocedure", + name="google_doc_id", + field=models.CharField( + blank=True, + help_text="Google Docs document ID", + max_length=100, + null=True, + ), + ), + migrations.AlterField( + model_name="historicalprocedure", + name="google_doc_url", + field=models.URLField( + blank=True, + help_text="URL to edit the document in Google Docs", + null=True, + ), + ), + migrations.AlterField( + model_name="historicalprocedure", + name="site_location", + field=models.CharField( + blank=True, help_text="Work site location", max_length=500, null=True + ), + ), + migrations.AlterField( + model_name="procedure", + name="google_doc_id", + field=models.CharField( + blank=True, + help_text="Google Docs document ID", + max_length=100, + null=True, + ), + ), + migrations.AlterField( + model_name="procedure", + name="google_doc_url", + field=models.URLField( + blank=True, + help_text="URL to edit the document in Google Docs", + null=True, + ), + ), + migrations.AlterField( + model_name="procedure", + name="site_location", + field=models.CharField( + blank=True, help_text="Work site location", max_length=500, null=True + ), + ), + migrations.RunSQL( + sql='UPDATE process_procedure SET "site_location" = NULL WHERE "site_location" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE process_procedure SET "google_doc_id" = NULL WHERE "google_doc_id" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE process_procedure SET "google_doc_url" = NULL WHERE "google_doc_url" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/process/migrations/0004_text_unset_constraints.py b/apps/process/migrations/0004_text_unset_constraints.py new file mode 100644 index 000000000..f6b255f5c --- /dev/null +++ b/apps/process/migrations/0004_text_unset_constraints.py @@ -0,0 +1,36 @@ +"""Add the not-blank CHECK constraints for process's newly nullable columns. + +Separate from 0003_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "process_procedure": [ + "google_doc_id", + "google_doc_url", + "site_location" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("process", "0003_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/process/models/procedure.py b/apps/process/models/procedure.py index e68a16d31..eef51e2c5 100644 --- a/apps/process/models/procedure.py +++ b/apps/process/models/procedure.py @@ -48,6 +48,7 @@ class Procedure(models.Model): site_location = models.CharField( max_length=500, blank=True, + null=True, help_text="Work site location", ) @@ -75,10 +76,12 @@ class Procedure(models.Model): google_doc_id = models.CharField( max_length=100, blank=True, + null=True, help_text="Google Docs document ID", ) google_doc_url = models.URLField( blank=True, + null=True, help_text="URL to edit the document in Google Docs", ) diff --git a/apps/process/serializers/form_serializer.py b/apps/process/serializers/form_serializer.py index 9f83d8e83..77a92ce1d 100644 --- a/apps/process/serializers/form_serializer.py +++ b/apps/process/serializers/form_serializer.py @@ -8,9 +8,10 @@ from rest_framework import serializers from apps.process.models import Form, FormEntry +from apps.workflow.serializers_base import NullUnsetModelSerializer -class FormEntrySerializer(serializers.ModelSerializer): +class FormEntrySerializer(NullUnsetModelSerializer[FormEntry]): """Serializer for FormEntry — filled-in instances of forms.""" staff_name = serializers.CharField( @@ -47,7 +48,7 @@ class Meta: read_only_fields = ["id", "form", "entered_by", "created_at"] -class FormListSerializer(serializers.ModelSerializer): +class FormListSerializer(NullUnsetModelSerializer[Form]): """List serializer for form endpoints — includes form_schema.""" entry_count = serializers.IntegerField(read_only=True, default=0) @@ -68,7 +69,7 @@ class Meta: ] -class FormDetailSerializer(serializers.ModelSerializer): +class FormDetailSerializer(NullUnsetModelSerializer[Form]): """Detail serializer for forms.""" class Meta: @@ -105,7 +106,7 @@ class FormCreateSerializer(serializers.Serializer): form_schema = serializers.JSONField(required=False, default=dict) -class FormUpdateSerializer(serializers.ModelSerializer): +class FormUpdateSerializer(NullUnsetModelSerializer[Form]): """Update serializer for forms.""" class Meta: diff --git a/apps/process/serializers/procedure_serializer.py b/apps/process/serializers/procedure_serializer.py index 0680e3adb..901adae7d 100644 --- a/apps/process/serializers/procedure_serializer.py +++ b/apps/process/serializers/procedure_serializer.py @@ -8,6 +8,7 @@ from rest_framework import serializers from apps.process.models import Procedure +from apps.workflow.serializers_base import NullUnsetModelSerializer class SWPGenerateRequestSerializer(serializers.Serializer): @@ -33,7 +34,7 @@ class SWPGenerateRequestSerializer(serializers.Serializer): ) -class ProcedureListSerializer(serializers.ModelSerializer): +class ProcedureListSerializer(NullUnsetModelSerializer[Procedure]): """List serializer for procedure endpoints — includes google_doc_url.""" job_number = serializers.CharField( @@ -57,7 +58,7 @@ class Meta: ] -class ProcedureDetailSerializer(serializers.ModelSerializer): +class ProcedureDetailSerializer(NullUnsetModelSerializer[Procedure]): """Detail serializer for procedures — adds google_doc_id, job_id.""" job_id = serializers.UUIDField(source="job.id", read_only=True, allow_null=True) @@ -107,7 +108,7 @@ class ProcedureCreateSerializer(serializers.Serializer): ) -class ProcedureUpdateSerializer(serializers.ModelSerializer): +class ProcedureUpdateSerializer(NullUnsetModelSerializer[Procedure]): """Update serializer for procedures.""" class Meta: diff --git a/apps/process/services/procedure_service.py b/apps/process/services/procedure_service.py index c95b05e4d..d075c4fb0 100644 --- a/apps/process/services/procedure_service.py +++ b/apps/process/services/procedure_service.py @@ -161,7 +161,7 @@ def generate_sop( job=None, document_number=document_number or None, title=sop_content.get("title", title), - site_location="", + site_location=None, google_doc_id=doc_result.document_id, google_doc_url=doc_result.edit_url, ) diff --git a/apps/process/tests/test_form_model.py b/apps/process/tests/test_form_model.py index 8c33e3b7d..3134a5f1d 100644 --- a/apps/process/tests/test_form_model.py +++ b/apps/process/tests/test_form_model.py @@ -5,7 +5,7 @@ @pytest.mark.django_db class TestForm: - def test_create_form_definition(self): + def test_create_form_definition(self) -> None: form = Form.objects.create( document_type="form", title="Ladder Inspection Checklist", @@ -15,13 +15,13 @@ def test_create_form_definition(self): assert form.status == "active" assert form.document_type == "form" - def test_filter_by_document_type(self): + def test_filter_by_document_type(self) -> None: Form.objects.create(document_type="form", title="Form") Form.objects.create(document_type="register", title="Reg") assert Form.objects.filter(document_type="form").count() == 1 assert Form.objects.filter(document_type="register").count() == 1 - def test_str_representation(self): + def test_str_representation(self) -> None: doc = Form.objects.create( document_type="form", title="Safety Checklist", @@ -32,7 +32,7 @@ def test_str_representation(self): @pytest.mark.django_db class TestFormEntry: - def test_create_entry(self): + def test_create_entry(self) -> None: doc = Form.objects.create( document_type="register", title="Maintenance Log", @@ -65,7 +65,7 @@ def test_entry_with_job(self, job): assert entry.job == job assert job.form_entries.count() == 1 - def test_cascade_delete(self): + def test_cascade_delete(self) -> None: doc = Form.objects.create( document_type="register", title="Maintenance Log", diff --git a/apps/process/tests/test_form_service.py b/apps/process/tests/test_form_service.py index eb2c813dc..cb3827277 100644 --- a/apps/process/tests/test_form_service.py +++ b/apps/process/tests/test_form_service.py @@ -4,14 +4,14 @@ from apps.process.services.form_service import FormService -def _make_service(): +def _make_service() -> FormService: """Create a FormService.""" return FormService() @pytest.mark.django_db class TestFormServiceCreateEntry: - def test_create_entry_returns_form_entry(self): + def test_create_entry_returns_form_entry(self) -> None: form = Form.objects.create( document_type="form", title="Inspection Form", @@ -36,7 +36,7 @@ def test_create_entry_with_job(self, job): assert entry.job == job - def test_create_entry_with_data(self): + def test_create_entry_with_data(self) -> None: form = Form.objects.create( document_type="form", title="Checklist", @@ -54,7 +54,7 @@ def test_create_entry_with_data(self): @pytest.mark.django_db class TestFormServiceCreateForm: - def test_create_form_happy_path(self): + def test_create_form_happy_path(self) -> None: service = _make_service() doc = service.create_form( title="Inspection Checklist", @@ -71,7 +71,7 @@ def test_create_form_happy_path(self): assert doc.status == "active" assert doc.form_schema == {"fields": [{"key": "item", "type": "text"}]} - def test_create_form_register_type(self): + def test_create_form_register_type(self) -> None: service = _make_service() doc = service.create_form( title="Chemical Register", @@ -80,7 +80,7 @@ def test_create_form_register_type(self): assert doc.document_type == "register" - def test_create_form_rejects_procedure_type(self): + def test_create_form_rejects_procedure_type(self) -> None: service = _make_service() with pytest.raises(ValueError, match="form.*register"): service.create_form( diff --git a/apps/process/tests/test_procedure_model.py b/apps/process/tests/test_procedure_model.py index 9bd32f8d5..88343f5a4 100644 --- a/apps/process/tests/test_procedure_model.py +++ b/apps/process/tests/test_procedure_model.py @@ -5,7 +5,7 @@ @pytest.mark.django_db class TestProcedure: - def test_create_procedure_with_tags(self): + def test_create_procedure_with_tags(self) -> None: doc = Procedure.objects.create( document_type="procedure", title="Drill Press SOP", @@ -16,7 +16,7 @@ def test_create_procedure_with_tags(self): assert "sop" in doc.tags assert doc.status == "active" - def test_filter_by_tags_contains(self): + def test_filter_by_tags_contains(self) -> None: Procedure.objects.create( document_type="procedure", title="SWP1", @@ -31,13 +31,13 @@ def test_filter_by_tags_contains(self): assert swps.count() == 1 assert swps.first().title == "SWP1" - def test_filter_by_document_type(self): + def test_filter_by_document_type(self) -> None: Procedure.objects.create(document_type="procedure", title="Proc") Procedure.objects.create(document_type="reference", title="Ref") assert Procedure.objects.filter(document_type="procedure").count() == 1 assert Procedure.objects.filter(document_type="reference").count() == 1 - def test_str_representation(self): + def test_str_representation(self) -> None: doc = Procedure.objects.create( document_type="procedure", title="MIG Welding", diff --git a/apps/process/tests/test_procedure_service.py b/apps/process/tests/test_procedure_service.py index 8c7635ad1..aa3a894c7 100644 --- a/apps/process/tests/test_procedure_service.py +++ b/apps/process/tests/test_procedure_service.py @@ -9,7 +9,7 @@ from apps.workflow.models import CompanyDefaults -def _make_service(): +def _make_service() -> ProcedureService: """Create a ProcedureService with external dependencies mocked out.""" with ( patch("apps.process.services.procedure_service.SafetyAIService"), @@ -26,7 +26,7 @@ def _create_shop_client(name: str = "Shop Company") -> Company: @pytest.mark.django_db class TestProcedureServiceCreateBlank: - def test_create_blank_procedure_happy_path(self): + def test_create_blank_procedure_happy_path(self) -> None: CompanyDefaults.objects.create( company_name="Morris Sheetmetal", gdrive_reference_library_folder_id="folder-123", @@ -61,7 +61,7 @@ def test_create_blank_procedure_happy_path(self): title="How to weld", folder_id="folder-123" ) - def test_create_blank_procedure_rejects_invalid_type(self): + def test_create_blank_procedure_rejects_invalid_type(self) -> None: service = _make_service() with pytest.raises(ValueError, match="Invalid document_type"): service.create_blank_procedure( @@ -69,10 +69,10 @@ def test_create_blank_procedure_rejects_invalid_type(self): title="Bad type", ) - def test_create_blank_procedure_rejects_missing_folder_config(self): + def test_create_blank_procedure_rejects_missing_folder_config(self) -> None: CompanyDefaults.objects.create( company_name="Test", - gdrive_reference_library_folder_id="", + gdrive_reference_library_folder_id=None, shop_company=_create_shop_client(), ) diff --git a/apps/purchasing/__init__.py b/apps/purchasing/__init__.py index cdeb407c7..3de4dd4b3 100644 --- a/apps/purchasing/__init__.py +++ b/apps/purchasing/__init__.py @@ -1,14 +1,13 @@ # This file is autogenerated by update_init.py script from .apps import PurchasingConfig -from .exceptions import PreconditionFailedError # Conditional imports (only when Django is ready) try: from django.apps import apps if apps.ready: - from .etag import generate_po_etag, normalize_etag + from .etag import generate_po_etag from .models import ( PurchaseOrder, PurchaseOrderEvent, @@ -47,6 +46,7 @@ PurchaseOrderLineSerializer, PurchaseOrderLineUpdateSerializer, PurchaseOrderListSerializer, + PurchaseOrderMutationErrorResponseSerializer, PurchaseOrderPDFResponseSerializer, PurchaseOrderUpdateResponseSerializer, PurchaseOrderUpdateSerializer, @@ -89,7 +89,6 @@ "JobForPurchasingSerializer", "ParseStockItemTask", "ParseUnparsedStockItemsTask", - "PreconditionFailedError", "ProductMappingListResponseSerializer", "ProductMappingSerializer", "ProductMappingValidateResponseSerializer", @@ -113,6 +112,7 @@ "PurchaseOrderLineSerializer", "PurchaseOrderLineUpdateSerializer", "PurchaseOrderListSerializer", + "PurchaseOrderMutationErrorResponseSerializer", "PurchaseOrderPDFResponseSerializer", "PurchaseOrderSupplierQuote", "PurchaseOrderUpdateResponseSerializer", @@ -135,7 +135,6 @@ "SupplierSearchResultSerializer", "enqueue_stock_metadata_parse", "generate_po_etag", - "normalize_etag", "stock_metadata_incomplete", "stock_metadata_parse_eligible", ] diff --git a/apps/purchasing/etag.py b/apps/purchasing/etag.py index 43f78d4f1..b7e0e26fb 100644 --- a/apps/purchasing/etag.py +++ b/apps/purchasing/etag.py @@ -1,30 +1,11 @@ -"""Helper utilities for Purchase Order ETag handling.""" +"""Purchase Order ETag generation.""" from __future__ import annotations -from typing import Optional - from apps.purchasing.models import PurchaseOrder - - -def normalize_etag(etag: Optional[str]) -> Optional[str]: - """Normalize an ETag header value for comparison.""" - if not etag: - return None - val = etag.strip() - if val.startswith("W/"): - val = val[2:].strip() - if len(val) >= 2 and ( - (val[0] == '"' and val[-1] == '"') or (val[0] == "'" and val[-1] == "'") - ): - val = val[1:-1] - return val or None +from apps.workflow.etag import generate_updated_at_etag def generate_po_etag(po: PurchaseOrder) -> str: - """Generate a weak ETag for a purchase order based on last modification time.""" - try: - ts_ms = int(po.updated_at.timestamp() * 1000) - except Exception: - ts_ms = 0 - return f'W/"po:{po.id}:{ts_ms}"' + """Generate a strong ETag for a Purchase Order.""" + return generate_updated_at_etag("po", po.id, po.updated_at) diff --git a/apps/purchasing/exceptions.py b/apps/purchasing/exceptions.py deleted file mode 100644 index 0a76b0b6c..000000000 --- a/apps/purchasing/exceptions.py +++ /dev/null @@ -1,5 +0,0 @@ -class PreconditionFailedError(Exception): - """Raised when optimistic concurrency preconditions are not met.""" - - -__all__ = ["PreconditionFailedError"] diff --git a/apps/purchasing/migrations/0003_normalise_blank_text_to_null.py b/apps/purchasing/migrations/0003_normalise_blank_text_to_null.py new file mode 100644 index 000000000..792e13b90 --- /dev/null +++ b/apps/purchasing/migrations/0003_normalise_blank_text_to_null.py @@ -0,0 +1,61 @@ +"""Collapse empty-string text values on nullable purchasing fields. + +Most of these columns are `null=True, blank=True`, so "unset" had two +representations and consumers had to test for each. NULL is the single +unset value; the serializers now reject "" so it cannot come back. + +`metal_type` carried three of them: NULL, "", and the MetalType.UNSPECIFIED +member — plus two rows holding 'steel', which is not a valid choice at all. +No row was ever actually NULL, so UNSPECIFIED was the de-facto unset and +the frontend translated it back to null on display. All four non-metal +values collapse to NULL here; the UNSPECIFIED member is removed from the +enum in the migration that follows, and 'steel' becomes NULL rather than +guessing which steel was meant. + +Irreversible: reverse cannot distinguish rows that were "" from rows that +were already NULL or UNSPECIFIED, so it is a no-op rather than a wrong +restore. +""" + +from django.db import migrations + +NULLABLE_COLUMNS_BY_TABLE = { + "purchasing_purchaseorderline": [ + "location", + "alloy", + "specifics", + "dimensions", + "item_code", + "supplier_item_code", + ], + "purchasing_purchaseorder": ["reference"], + "purchasing_stock": ["alloy", "specifics"], +} + +# Stock.metal_type is still NOT NULL at this point, so it cannot be cleared +# here; it is relaxed and then cleared in 0005. +METAL_TYPE_TABLES = ["purchasing_purchaseorderline"] + + +class Migration(migrations.Migration): + dependencies = [ + ("purchasing", "0002_drop_legacy_stock_xero_id_dupe"), + ] + + operations = [ + migrations.RunSQL( + sql=f"UPDATE {table} SET {col} = NULL WHERE {col} = ''", + reverse_sql=migrations.RunSQL.noop, + ) + for table, columns in NULLABLE_COLUMNS_BY_TABLE.items() + for col in columns + ] + [ + migrations.RunSQL( + sql=( + f"UPDATE {table} SET metal_type = NULL " + f"WHERE metal_type IN ('', 'steel', 'unspecified')" + ), + reverse_sql=migrations.RunSQL.noop, + ) + for table in METAL_TYPE_TABLES + ] diff --git a/apps/purchasing/migrations/0004_forbid_blank_text.py b/apps/purchasing/migrations/0004_forbid_blank_text.py new file mode 100644 index 000000000..37458d47b --- /dev/null +++ b/apps/purchasing/migrations/0004_forbid_blank_text.py @@ -0,0 +1,61 @@ +"""Forbid empty strings in nullable text columns (purchasing). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "purchasing_purchaseorder": [ + "online_url", + "reference", + "xero_tenant_id" + ], + "purchasing_purchaseorderline": [ + "alloy", + "dimensions", + "item_code", + "location", + "metal_type", + "specifics", + "supplier_item_code" + ], + "purchasing_stock": [ + "alloy", + "item_code", + "parser_version", + "specifics", + "xero_id" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("purchasing", "0003_normalise_blank_text_to_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/purchasing/migrations/0005_metal_type_null_is_unset.py b/apps/purchasing/migrations/0005_metal_type_null_is_unset.py new file mode 100644 index 000000000..4f5a3d22a --- /dev/null +++ b/apps/purchasing/migrations/0005_metal_type_null_is_unset.py @@ -0,0 +1,81 @@ +"""Make NULL the only unset for metal_type, dropping the UNSPECIFIED member. + +MetalType.UNSPECIFIED was a second way of saying "no metal" alongside NULL, +and the frontend translated it back to null on display. Removing the member +leaves NULL as the single unset, in line with the rule in CLAUDE.md. + +Stock.metal_type is relaxed to nullable here rather than in 0003 because it +was NOT NULL until this migration, so its rows could not be cleared earlier. +The CHECK constraint is added afterwards for the same reason: the column +only joins the nullable-text set once it is nullable. +""" + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("purchasing", "0004_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="purchaseorderline", + name="metal_type", + field=models.CharField( + blank=True, + choices=[ + ("stainless_steel", "Stainless Steel"), + ("mild_steel", "Mild Steel"), + ("aluminium", "Aluminium"), + ("brass", "Brass"), + ("copper", "Copper"), + ("titanium", "Titanium"), + ("zinc", "Zinc"), + ("galvanized", "Galvanized"), + ("other", "Other"), + ], + max_length=100, + null=True, + ), + ), + migrations.AlterField( + model_name="stock", + name="metal_type", + field=models.CharField( + blank=True, + choices=[ + ("stainless_steel", "Stainless Steel"), + ("mild_steel", "Mild Steel"), + ("aluminium", "Aluminium"), + ("brass", "Brass"), + ("copper", "Copper"), + ("titanium", "Titanium"), + ("zinc", "Zinc"), + ("galvanized", "Galvanized"), + ("other", "Other"), + ], + help_text="Type of metal", + max_length=100, + null=True, + ), + ), + migrations.RunSQL( + sql=( + "UPDATE purchasing_stock SET metal_type = NULL " + "WHERE metal_type IN ('', 'steel', 'unspecified')" + ), + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql=( + "ALTER TABLE purchasing_stock " + "ADD CONSTRAINT metal_type_not_blank CHECK (metal_type <> '')" + ), + reverse_sql=( + "ALTER TABLE purchasing_stock " + "DROP CONSTRAINT IF EXISTS metal_type_not_blank" + ), + ), + ] diff --git a/apps/purchasing/migrations/0006_stock_location_nullable.py b/apps/purchasing/migrations/0006_stock_location_nullable.py new file mode 100644 index 000000000..e6df0d63b --- /dev/null +++ b/apps/purchasing/migrations/0006_stock_location_nullable.py @@ -0,0 +1,41 @@ +# Generated by Django 6.0.7 on 2026-07-27 21:58 + +"""Make Stock.location nullable so location has one spelling of "unset". + +PurchaseOrderLine.location was already `null=True, blank=True` while +Stock.location was NOT NULL with blank=True, so the same concept used NULL +in one table and "" in the other. NULL wins, per the rule in CLAUDE.md. +""" + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("purchasing", "0005_metal_type_null_is_unset"), + ] + + operations = [ + migrations.AlterField( + model_name="stock", + name="location", + field=models.TextField( + blank=True, help_text="Where we are keeping this", null=True + ), + ), + migrations.RunSQL( + sql="UPDATE purchasing_stock SET location = NULL WHERE location = ''", + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql=( + "ALTER TABLE purchasing_stock " + "ADD CONSTRAINT location_not_blank CHECK (location <> '')" + ), + reverse_sql=( + "ALTER TABLE purchasing_stock " + "DROP CONSTRAINT IF EXISTS location_not_blank" + ), + ), + ] diff --git a/apps/purchasing/migrations/0007_text_unset_is_null.py b/apps/purchasing/migrations/0007_text_unset_is_null.py new file mode 100644 index 000000000..9db7e8f1c --- /dev/null +++ b/apps/purchasing/migrations/0007_text_unset_is_null.py @@ -0,0 +1,32 @@ +"""Give purchasing's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("purchasing", "0006_stock_location_nullable"), + ] + + operations = [ + migrations.AlterField( + model_name="purchaseordersupplierquote", + name="mime_type", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.RunSQL( + sql='UPDATE purchasing_purchaseordersupplierquote SET "mime_type" = NULL WHERE "mime_type" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/purchasing/migrations/0008_text_unset_constraints.py b/apps/purchasing/migrations/0008_text_unset_constraints.py new file mode 100644 index 000000000..df7b9c41f --- /dev/null +++ b/apps/purchasing/migrations/0008_text_unset_constraints.py @@ -0,0 +1,34 @@ +"""Add the not-blank CHECK constraints for purchasing's newly nullable columns. + +Separate from 0007_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "purchasing_purchaseordersupplierquote": [ + "mime_type" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("purchasing", "0007_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/purchasing/models.py b/apps/purchasing/models.py index 5f5f49ad3..8dac53c48 100644 --- a/apps/purchasing/models.py +++ b/apps/purchasing/models.py @@ -274,7 +274,6 @@ class PurchaseOrderLine(models.Model): metal_type = models.CharField( max_length=100, choices=MetalType.choices, - default=MetalType.UNSPECIFIED, blank=True, null=True, ) @@ -329,7 +328,7 @@ class PurchaseOrderSupplierQuote(models.Model): ) filename = models.CharField(max_length=255) file_path = models.CharField(max_length=500) - mime_type = models.CharField(max_length=100, blank=True) + mime_type = models.CharField(max_length=100, blank=True, null=True) uploaded_at = models.DateTimeField(auto_now_add=True) extracted_data = models.JSONField( null=True, blank=True, help_text="Extracted data from the quote" @@ -495,12 +494,14 @@ class Stock(models.Model): related_name="child_stock_splits", help_text="The parent stock item this was split from (if source='split_from_stock')", ) - location = models.TextField(blank=True, help_text="Where we are keeping this") + location = models.TextField( + blank=True, null=True, help_text="Where we are keeping this" + ) metal_type = models.CharField( max_length=100, choices=MetalType.choices, - default=MetalType.UNSPECIFIED, blank=True, + null=True, help_text="Type of metal", ) alloy = models.CharField( diff --git a/apps/purchasing/serializers.py b/apps/purchasing/serializers.py index 270e33f6a..7161f3daa 100644 --- a/apps/purchasing/serializers.py +++ b/apps/purchasing/serializers.py @@ -12,6 +12,8 @@ PurchaseOrderLine, Stock, ) +from apps.workflow.serializers import AppErrorResponseSerializer +from apps.workflow.serializers_base import NullUnsetModelSerializer class SupplierPriceStatusItemSerializer(serializers.Serializer): @@ -59,7 +61,7 @@ class SupplierSearchResponseSerializer(serializers.Serializer): total_pages = serializers.IntegerField() -class JobForPurchasingSerializer(serializers.ModelSerializer): +class JobForPurchasingSerializer(NullUnsetModelSerializer[Job]): """Serializer for Job model in purchasing contexts.""" company_name = serializers.CharField( @@ -90,7 +92,7 @@ class Meta: ] -class PurchaseOrderLineSerializer(serializers.ModelSerializer): +class PurchaseOrderLineSerializer(NullUnsetModelSerializer[PurchaseOrderLine]): """Serializer for PurchaseOrderLine model.""" job_id = serializers.UUIDField(source="job.id", read_only=True, allow_null=True) @@ -129,7 +131,7 @@ def get_times_used(self, obj: PurchaseOrderLine) -> int: ) -class PurchaseOrderDetailSerializer(serializers.ModelSerializer): +class PurchaseOrderDetailSerializer(NullUnsetModelSerializer[PurchaseOrder]): """Return purchase order details with related lines.""" supplier = serializers.CharField( @@ -359,7 +361,7 @@ class PurchaseOrderAllocationsResponseSerializer(serializers.Serializer): ) -class StockItemSerializer(serializers.ModelSerializer): +class StockItemSerializer(NullUnsetModelSerializer[Stock]): """Serializer for individual stock items.""" job_id = serializers.UUIDField(read_only=True, allow_null=True) @@ -463,6 +465,12 @@ class PurchasingErrorResponseSerializer(serializers.Serializer): ) +class PurchaseOrderMutationErrorResponseSerializer(AppErrorResponseSerializer): + """Exception-derived error response for PO mutations and receipts.""" + + success = serializers.BooleanField(required=False, default=False) + + # Purchase Order Email and PDF serializers class PurchaseOrderEmailSerializer(serializers.Serializer): """Serializer for purchase order email generation request""" @@ -597,7 +605,7 @@ class ProductMappingValidateResponseSerializer(serializers.Serializer): # Purchase Order Event serializers -class PurchaseOrderEventSerializer(serializers.ModelSerializer): +class PurchaseOrderEventSerializer(NullUnsetModelSerializer[PurchaseOrderEvent]): """Serializer for PurchaseOrderEvent model - read-only for frontend.""" staff = serializers.CharField( diff --git a/apps/purchasing/services/allocation_service.py b/apps/purchasing/services/allocation_service.py index 24ceda806..cf7ae0a12 100644 --- a/apps/purchasing/services/allocation_service.py +++ b/apps/purchasing/services/allocation_service.py @@ -26,7 +26,7 @@ class DeletionResult: success: bool message: str deleted_quantity: float - description: str + description: str | None updated_received_quantity: float job_name: str | None = None diff --git a/apps/purchasing/services/delivery_receipt_service.py b/apps/purchasing/services/delivery_receipt_service.py index 9f2a990c4..7a3268c84 100644 --- a/apps/purchasing/services/delivery_receipt_service.py +++ b/apps/purchasing/services/delivery_receipt_service.py @@ -9,13 +9,14 @@ from django.utils import timezone from apps.job.models import CostLine, CostSet, Job -from apps.purchasing.etag import generate_po_etag, normalize_etag -from apps.purchasing.exceptions import PreconditionFailedError +from apps.purchasing.etag import generate_po_etag from apps.purchasing.models import PurchaseOrder, PurchaseOrderLine, Stock from apps.purchasing.tasks import ( enqueue_stock_metadata_parse, stock_metadata_parse_eligible, ) +from apps.workflow.etag import if_match_satisfied +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.models.company_defaults import CompanyDefaults from apps.workflow.services.error_persistence import persist_app_error from apps.workflow.services.validation import to_decimal @@ -197,10 +198,10 @@ def _create_stock_from_allocation( quantity=qty, unit_cost=line.unit_cost, retail_rate=retail_rate, - metal_type=metadata.get("metal_type", line.metal_type or "unspecified"), - alloy=metadata.get("alloy", line.alloy or ""), - specifics=metadata.get("specifics", line.specifics or ""), - location=metadata.get("location", line.location or ""), + metal_type=metadata.get("metal_type", line.metal_type) or None, + alloy=metadata.get("alloy", line.alloy) or None, + specifics=metadata.get("specifics", line.specifics) or None, + location=metadata.get("location", line.location) or None, date=timezone.now(), source="purchase_order", source_purchase_order_line=line, @@ -306,7 +307,7 @@ def process_delivery_receipt( line_allocations: dict, staff, *, - expected_etag: str | None = None, + expected_etag: str, ) -> PurchaseOrder: """ Process a delivery receipt: @@ -319,20 +320,16 @@ def process_delivery_receipt( logger.info("Starting delivery receipt processing for PO ID: %s", purchase_order_id) logger.debug("Received line_allocations: %s", line_allocations) - STOCK_HOLDING_JOB_ID = Stock.get_stock_holding_job().id - run_id = f"dr-{uuid.uuid4()}" try: with transaction.atomic(): po, lines_by_id = _load_po_and_lines(purchase_order_id, line_allocations) - expected_normalized = ( - normalize_etag(expected_etag) if expected_etag else None - ) - current_etag = normalize_etag(generate_po_etag(po)) - if expected_normalized is not None and expected_normalized != current_etag: + current_etag = generate_po_etag(po) + if not if_match_satisfied(expected_etag, current_etag): raise PreconditionFailedError( "Purchase order modified since it was fetched." ) + stock_holding_job_id = Stock.get_stock_holding_job().id jobs_by_id = _load_jobs(line_allocations) if po.status not in ("submitted", "partially_received", "fully_received"): @@ -381,7 +378,7 @@ def process_delivery_receipt( qty = alloc["quantity"] retail_rate_pct = alloc["retail_rate_pct"] - if str(job.id) == str(STOCK_HOLDING_JOB_ID): + if str(job.id) == str(stock_holding_job_id): _create_stock_from_allocation( purchase_order=po, line=line, @@ -409,22 +406,11 @@ def process_delivery_receipt( ) return po - except DeliveryReceiptValidationError: - # Let DRF/view layer convert to proper 4xx; message already specific - logger.exception( - "Validation Error processing delivery receipt for PO %s", purchase_order_id - ) - raise - except PurchaseOrder.DoesNotExist: - logger.exception( - "PO %s not found during delivery receipt processing", purchase_order_id - ) - raise - except Exception as e: - persist_app_error(e) + except Exception as exc: + persist_app_error(exc) logger.exception( - "Unexpected error processing delivery receipt for PO %s: %s", + "Error processing delivery receipt for PO %s: %s", purchase_order_id, - str(e), + str(exc), ) raise diff --git a/apps/purchasing/services/purchasing_rest_service.py b/apps/purchasing/services/purchasing_rest_service.py index 9d46c04e3..1afb8183a 100644 --- a/apps/purchasing/services/purchasing_rest_service.py +++ b/apps/purchasing/services/purchasing_rest_service.py @@ -16,8 +16,7 @@ from apps.company.models import Supplier, SupplierPickupAddress from apps.job.models.costing import CostLine from apps.job.models.job import Job -from apps.purchasing.etag import generate_po_etag, normalize_etag -from apps.purchasing.exceptions import PreconditionFailedError +from apps.purchasing.etag import generate_po_etag from apps.purchasing.models import PurchaseOrder, PurchaseOrderLine, Stock from apps.purchasing.services.delivery_receipt_service import ( _create_costline_from_allocation, @@ -27,6 +26,8 @@ enqueue_stock_metadata_parse, stock_metadata_parse_eligible, ) +from apps.workflow.etag import if_match_satisfied +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.models import CompanyDefaults logger = logging.getLogger(__name__) @@ -44,11 +45,12 @@ class PurchasingRestService: line, "unit_cost", Decimal(str(value)) if value is not None else None ), "price_tbc": lambda line, value: setattr(line, "price_tbc", bool(value)), - "metal_type": lambda line, value: setattr(line, "metal_type", value or ""), - "alloy": lambda line, value: setattr(line, "alloy", value or ""), - "specifics": lambda line, value: setattr(line, "specifics", value or ""), - "location": lambda line, value: setattr(line, "location", value or ""), - "dimensions": lambda line, value: setattr(line, "dimensions", value or ""), + # A cleared field is unset, i.e. NULL. + "metal_type": lambda line, value: setattr(line, "metal_type", value or None), + "alloy": lambda line, value: setattr(line, "alloy", value or None), + "specifics": lambda line, value: setattr(line, "specifics", value or None), + "location": lambda line, value: setattr(line, "location", value or None), + "dimensions": lambda line, value: setattr(line, "dimensions", value or None), } @staticmethod @@ -139,12 +141,12 @@ def _create_line( else None ), price_tbc=bool(line_data.get("price_tbc", False)), - item_code=line_data.get("item_code"), - metal_type=line_data.get("metal_type", ""), - alloy=line_data.get("alloy", ""), - specifics=line_data.get("specifics", ""), - location=line_data.get("location", ""), - dimensions=line_data.get("dimensions", ""), + item_code=line_data.get("item_code") or None, + metal_type=line_data.get("metal_type") or None, + alloy=line_data.get("alloy") or None, + specifics=line_data.get("specifics") or None, + location=line_data.get("location") or None, + dimensions=line_data.get("dimensions") or None, ) logger.info(f"Created new line for PO {po.id}") @@ -399,11 +401,11 @@ def create_purchase_order( ), unit_cost=unit_cost, price_tbc=price_tbc, - item_code=line.get("item_code"), - metal_type=line.get("metal_type", ""), - alloy=line.get("alloy", ""), - specifics=line.get("specifics", ""), - location=line.get("location", ""), + item_code=line.get("item_code") or None, + metal_type=line.get("metal_type") or None, + alloy=line.get("alloy") or None, + specifics=line.get("specifics") or None, + location=line.get("location") or None, ) return po @@ -413,10 +415,8 @@ def update_purchase_order( data: Dict[str, Any], *, staff, - expected_etag: str | None = None, + expected_etag: str, ) -> PurchaseOrder: - expected_normalized = normalize_etag(expected_etag) if expected_etag else None - with transaction.atomic(): try: po = ( @@ -428,8 +428,8 @@ def update_purchase_order( except PurchaseOrder.DoesNotExist as exc: raise Http404(f"PurchaseOrder {po_id} not found") from exc - current_etag = normalize_etag(generate_po_etag(po)) - if expected_normalized is not None and expected_normalized != current_etag: + current_etag = generate_po_etag(po) + if not if_match_satisfied(expected_etag, current_etag): raise PreconditionFailedError( "Purchase order modified since it was fetched." ) @@ -493,10 +493,10 @@ def create_stock(data: dict) -> Stock: quantity=Decimal(str(data["quantity"])), unit_cost=Decimal(str(data["unit_cost"])), source=data["source"], - metal_type=data.get("metal_type", ""), - alloy=data.get("alloy", ""), - specifics=data.get("specifics", ""), - location=data.get("location", ""), + metal_type=data.get("metal_type") or None, + alloy=data.get("alloy") or None, + specifics=data.get("specifics") or None, + location=data.get("location") or None, is_active=True, ) diff --git a/apps/purchasing/services/quote_to_po_service.py b/apps/purchasing/services/quote_to_po_service.py index f66cf7d49..3bdeef4bf 100644 --- a/apps/purchasing/services/quote_to_po_service.py +++ b/apps/purchasing/services/quote_to_po_service.py @@ -48,8 +48,8 @@ def normalize_quote_metal_type(value: object) -> MetalType | None: if isinstance(value, str) and value in MetalType.values: return MetalType(value) - logger.warning("Invalid quote metal_type %r; using unspecified", value) - return MetalType.UNSPECIFIED + logger.warning("Invalid quote metal_type %r; leaving unset", value) + return None class SupplierQuoteBaseModel(BaseModel): @@ -538,11 +538,11 @@ def create_po_line_from_quote_item( quantity=quantity, unit_cost=unit_cost, price_tbc=unit_cost is None, - supplier_item_code=line_data.supplier_item_code or "", - metal_type=line_data.metal_type or MetalType.UNSPECIFIED, - alloy=line_data.alloy or "", - specifics=line_data.specifics or "", - dimensions=line_data.dimensions or "", + supplier_item_code=line_data.supplier_item_code or None, + metal_type=line_data.metal_type or None, + alloy=line_data.alloy or None, + specifics=line_data.specifics or None, + dimensions=line_data.dimensions or None, raw_line_data=line_data.model_dump(mode="json", exclude_none=True), ) diff --git a/apps/purchasing/tasks.py b/apps/purchasing/tasks.py index eb58300ce..25a471a2d 100644 --- a/apps/purchasing/tasks.py +++ b/apps/purchasing/tasks.py @@ -29,24 +29,13 @@ def delay(self, limit: int = 50) -> object: ... def _incomplete_stock_metadata_query() -> Q: return ( - Q(alloy__isnull=True) - | Q(alloy="") - | Q(specifics__isnull=True) - | Q(specifics="") - | Q(metal_type__isnull=True) - | Q(metal_type="") - | Q(metal_type="unspecified") + Q(alloy__isnull=True) | Q(specifics__isnull=True) | Q(metal_type__isnull=True) ) def stock_metadata_incomplete(stock: Stock) -> bool: """Return True when stock is missing metadata the parser can infer.""" - return ( - not stock.alloy - or not stock.specifics - or not stock.metal_type - or stock.metal_type == "unspecified" - ) + return not stock.alloy or not stock.specifics or not stock.metal_type def stock_metadata_parse_eligible(stock: Stock, *, force: bool = False) -> bool: diff --git a/apps/purchasing/tests/test_purchase_order_etags.py b/apps/purchasing/tests/test_purchase_order_etags.py new file mode 100644 index 000000000..8d5fffcf0 --- /dev/null +++ b/apps/purchasing/tests/test_purchase_order_etags.py @@ -0,0 +1,107 @@ +"""Purchase-order mutations must enforce full-precision optimistic concurrency.""" + +from datetime import timedelta + +from django.utils import timezone + +from apps.purchasing.etag import generate_po_etag +from apps.purchasing.models import PurchaseOrder +from apps.testing import BaseAPITestCase +from apps.workflow.models import AppError + + +class PurchaseOrderETagTests(BaseAPITestCase): + def setUp(self) -> None: + super().setUp() + self.client.force_authenticate(self.test_staff) + self.purchase_order = PurchaseOrder.objects.create( + po_number="PO-ETAG-1", + created_by=self.test_staff, + ) + self.detail_url = f"/api/purchasing/purchase-orders/{self.purchase_order.id}/" + + def _current_etag(self) -> str: + response = self.client.get(self.detail_url) + self.assertEqual(response.status_code, 200) + return response["ETag"] + + def test_patch_requires_if_match(self) -> None: + response = self.client.patch(self.detail_url, {}, format="json") + + self.assertEqual(response.status_code, 428) + + def test_patch_rejects_a_stale_etag(self) -> None: + stale_etag = self._current_etag() + PurchaseOrder.objects.filter(pk=self.purchase_order.pk).update( + reference="Concurrent edit", + updated_at=timezone.now(), + ) + + response = self.client.patch( + self.detail_url, + {"reference": "Overwriting edit"}, + format="json", + HTTP_IF_MATCH=stale_etag, + ) + + self.assertEqual(response.status_code, 412) + self.assertIn("error_id", response.json()["details"]) + self.purchase_order.refresh_from_db() + self.assertEqual(self.purchase_order.reference, "Concurrent edit") + + def test_delivery_receipt_requires_if_match(self) -> None: + response = self.client.post( + "/api/purchasing/delivery-receipts/", + { + "purchase_order_id": str(self.purchase_order.id), + "allocations": {}, + }, + format="json", + ) + + self.assertEqual(response.status_code, 428) + + def test_delivery_receipt_rejects_a_stale_etag(self) -> None: + stale_etag = self._current_etag() + PurchaseOrder.objects.filter(pk=self.purchase_order.pk).update( + reference="Concurrent receipt edit", + updated_at=timezone.now(), + ) + + response = self.client.post( + "/api/purchasing/delivery-receipts/", + { + "purchase_order_id": str(self.purchase_order.id), + "allocations": {}, + }, + format="json", + HTTP_IF_MATCH=stale_etag, + ) + + self.assertEqual(response.status_code, 412) + self.assertIn("error_id", response.json()["details"]) + self.assertEqual(AppError.objects.count(), 1) + + def test_patch_rejects_a_weak_if_match_tag(self) -> None: + current_etag = self._current_etag() + initial_reference = self.purchase_order.reference + + response = self.client.patch( + self.detail_url, + {"reference": "Weak edit"}, + format="json", + HTTP_IF_MATCH=f"W/{current_etag}", + ) + + self.assertEqual(response.status_code, 412) + self.purchase_order.refresh_from_db() + self.assertEqual(self.purchase_order.reference, initial_reference) + + def test_etag_distinguishes_changes_within_one_millisecond(self) -> None: + first_timestamp = self.purchase_order.updated_at + self.purchase_order.updated_at = first_timestamp + timedelta(microseconds=100) + + later_etag = generate_po_etag(self.purchase_order) + self.purchase_order.updated_at = first_timestamp + + self.assertNotEqual(generate_po_etag(self.purchase_order), later_etag) diff --git a/apps/purchasing/tests/test_purchase_order_line_usage.py b/apps/purchasing/tests/test_purchase_order_line_usage.py index 3793f63aa..c1ea53cbe 100644 --- a/apps/purchasing/tests/test_purchase_order_line_usage.py +++ b/apps/purchasing/tests/test_purchase_order_line_usage.py @@ -21,7 +21,7 @@ def company_defaults(db: None) -> None: @pytest.fixture -def auth_api(db, company_defaults): +def auth_api(db: None, company_defaults: None) -> APIClient: staff = Staff.objects.create( email="po-line-usage@example.test", first_name="Purchase", @@ -57,7 +57,9 @@ def _material_usage(item_code: str) -> None: @pytest.mark.django_db -def test_purchase_order_detail_lines_include_material_times_used(auth_api): +def test_purchase_order_detail_lines_include_material_times_used( + auth_api: APIClient, +) -> None: po = PurchaseOrder.objects.create(po_number="PO-USAGE-1") used_line = PurchaseOrderLine.objects.create( purchase_order=po, @@ -78,7 +80,7 @@ def test_purchase_order_detail_lines_include_material_times_used(auth_api): description="Blank item", quantity=Decimal("1.00"), unit_cost=Decimal("10.00"), - item_code="", + item_code=None, ) _material_usage("ABC-123") diff --git a/apps/purchasing/tests/test_quote_to_po_service.py b/apps/purchasing/tests/test_quote_to_po_service.py index 155c1e2b9..8b1d97f1b 100644 --- a/apps/purchasing/tests/test_quote_to_po_service.py +++ b/apps/purchasing/tests/test_quote_to_po_service.py @@ -12,7 +12,6 @@ from pydantic import ValidationError from apps.company.models import Company -from apps.job.enums import MetalType from apps.purchasing.models import PurchaseOrder, PurchaseOrderSupplierQuote from apps.purchasing.services.quote_to_po_service import ( SupplierQuoteItemModel, @@ -301,11 +300,11 @@ def test_create_po_from_quote_normalizes_invalid_metal_type( assert result == purchase_order quote.refresh_from_db() saved_quote_data = SupplierQuotePayloadModel.model_validate(quote.extracted_data) - assert saved_quote_data.line_items[0].metal_type == MetalType.UNSPECIFIED + assert saved_quote_data.line_items[0].metal_type is None line = purchase_order.po_lines.get() - assert line.metal_type == MetalType.UNSPECIFIED + assert line.metal_type is None saved_line_data = SupplierQuoteItemModel.model_validate(line.raw_line_data) - assert saved_line_data.metal_type == MetalType.UNSPECIFIED + assert saved_line_data.metal_type is None def test_create_po_from_quote_returns_already_logged_extraction_error( diff --git a/apps/purchasing/tests/test_serializers.py b/apps/purchasing/tests/test_serializers.py index cb7c48b51..03e01f097 100644 --- a/apps/purchasing/tests/test_serializers.py +++ b/apps/purchasing/tests/test_serializers.py @@ -12,7 +12,7 @@ class JobForPurchasingSerializerTests(BaseTestCase): - def test_client_name_uses_related_client_name(self): + def test_client_name_uses_related_client_name(self) -> None: company = Company.objects.create( name="Serializer Company", xero_last_modified=timezone.now(), @@ -31,7 +31,7 @@ def test_client_name_uses_related_client_name(self): class PurchaseOrderDetailSerializerTests(BaseTestCase): - def test_related_display_fields_use_related_objects(self): + def test_related_display_fields_use_related_objects(self) -> None: supplier = Company.objects.create( name="Serializer Supplier", xero_contact_id="00000000-0000-0000-0000-000000000001", @@ -54,7 +54,7 @@ def test_related_display_fields_use_related_objects(self): ) self.assertTrue(data["supplier_has_xero_id"]) - def test_missing_related_display_fields_keep_api_defaults(self): + def test_missing_related_display_fields_keep_api_defaults(self) -> None: purchase_order = PurchaseOrder.objects.create( po_number="PO-SERIALIZER-002", order_date=timezone.localdate(), diff --git a/apps/purchasing/tests/test_stock_fts_search.py b/apps/purchasing/tests/test_stock_fts_search.py index 142bd9b77..3adb3c543 100644 --- a/apps/purchasing/tests/test_stock_fts_search.py +++ b/apps/purchasing/tests/test_stock_fts_search.py @@ -10,6 +10,7 @@ from datetime import date from decimal import Decimal +from typing import TypedDict, Unpack import pytest from django.utils import timezone @@ -29,13 +30,24 @@ from apps.workflow.models import CompanyDefaults, SearchTelemetryEvent -def _stock(**overrides): - defaults = dict( - description="Generic material", - quantity=Decimal("1.00"), - unit_cost=Decimal("10.00"), - is_active=True, - ) +class _StockOverrides(TypedDict, total=False): + description: str + quantity: Decimal + unit_cost: Decimal + is_active: bool + item_code: str | None + metal_type: str | None + alloy: str | None + specifics: str | None + + +def _stock(**overrides: Unpack[_StockOverrides]) -> Stock: + defaults: _StockOverrides = { + "description": "Generic material", + "quantity": Decimal("1.00"), + "unit_cost": Decimal("10.00"), + "is_active": True, + } defaults.update(overrides) return Stock.objects.create(**defaults) @@ -129,7 +141,7 @@ def test_search_matches_specifics_field(db): _stock( description="Socket screw", specifics="m8 countersunk socket screw", - alloy="", + alloy=None, ) result = list_stock(query="countersunk", page=1, page_size=10) @@ -280,7 +292,7 @@ def test_galvanised_sheet_queries_surface_expected_material(db, query): class TestStockSearchHistoricalRanking(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.client_obj = Company.objects.create( name="Stock Search Company", @@ -308,7 +320,7 @@ def _record_material_usage( meta={"item_code": item_code}, ) - def test_unspecified_alloy_prefers_more_frequently_used_variant(self): + def test_unspecified_alloy_prefers_more_frequently_used_variant(self) -> None: preferred = _stock( item_code="SS-304-1.5-1219X2438", description="1.5X1219X2438 3042B SS SHT FIBRE PE", @@ -331,7 +343,7 @@ def test_unspecified_alloy_prefers_more_frequently_used_variant(self): assert preferred.description in _top_descriptions("1.5 stainless") - def test_explicit_alloy_overrides_historical_popularity(self): + def test_explicit_alloy_overrides_historical_popularity(self) -> None: _stock( item_code="SS-304-1.5-1219X2438", description="1.5X1219X2438 3042B SS SHT FIBRE PE", diff --git a/apps/purchasing/tests/test_stock_metadata_tasks.py b/apps/purchasing/tests/test_stock_metadata_tasks.py index f8a387e6c..56ce94718 100644 --- a/apps/purchasing/tests/test_stock_metadata_tasks.py +++ b/apps/purchasing/tests/test_stock_metadata_tasks.py @@ -72,7 +72,7 @@ def _xero_item(**overrides: Any) -> SimpleNamespace: @pytest.mark.django_db def test_stock_metadata_incomplete_detects_missing_fields() -> None: - incomplete = _stock(alloy="", metal_type="unspecified", specifics="") + incomplete = _stock(alloy=None, metal_type=None, specifics=None) complete = _stock( item_code="COMPLETE-5005", alloy="5005", @@ -176,7 +176,7 @@ def test_parse_unparsed_stock_items_task_queues_bounded_incomplete_batch() -> No @pytest.mark.django_db def test_auto_parse_stock_item_saves_valid_metadata_and_attempt() -> None: - stock = _stock(metal_type="unspecified", alloy="", specifics="") + stock = _stock(metal_type=None, alloy=None, specifics=None) parsed_data = { "metal_type": "aluminium", "alloy": "5005", @@ -200,7 +200,7 @@ def test_auto_parse_stock_item_saves_valid_metadata_and_attempt() -> None: @pytest.mark.django_db def test_auto_parse_stock_item_normalises_gemini_american_metal_spelling() -> None: - stock = _stock(metal_type="unspecified", alloy="", specifics="") + stock = _stock(metal_type=None, alloy=None, specifics=None) parsed_data = { "metal_type": "aluminum", "alloy": "5005", @@ -219,7 +219,7 @@ def test_auto_parse_stock_item_normalises_gemini_american_metal_spelling() -> No @pytest.mark.django_db def test_auto_parse_stock_item_records_attempt_without_retrying_empty_result() -> None: - stock = _stock(metal_type="unspecified", alloy="", specifics="") + stock = _stock(metal_type=None, alloy=None, specifics=None) with patch("apps.quoting.services.stock_parser.ProductParser") as parser_class: parser_class.return_value.parse_product.return_value = ({}, False) @@ -233,7 +233,7 @@ def test_auto_parse_stock_item_records_attempt_without_retrying_empty_result() - @pytest.mark.django_db def test_auto_parse_stock_item_does_not_record_attempt_on_parser_exception() -> None: - stock = _stock(metal_type="unspecified", alloy="", specifics="") + stock = _stock(metal_type=None, alloy=None, specifics=None) with ( patch("apps.quoting.services.stock_parser.ProductParser") as parser_class, @@ -253,7 +253,7 @@ def test_auto_parse_stock_item_does_not_record_attempt_on_parser_exception() -> @pytest.mark.django_db def test_failed_gemini_parse_is_not_requeued_by_backfill() -> None: - stock = _stock(metal_type="unspecified", alloy="", specifics="") + stock = _stock(metal_type=None, alloy=None, specifics=None) with patch("apps.purchasing.tasks.parse_stock_item_task.delay") as delay: parse_unparsed_stock_items_task(limit=50) @@ -276,7 +276,7 @@ def test_failed_gemini_parse_is_not_requeued_by_backfill() -> None: @pytest.mark.django_db def test_auto_parse_stock_item_rejects_hallucinated_alloy() -> None: - stock = _stock(description="2.0X1200X3000 AL SHTPE", alloy="") + stock = _stock(description="2.0X1200X3000 AL SHTPE", alloy=None) parsed_data = { "metal_type": "aluminium", "alloy": "5005", @@ -291,7 +291,7 @@ def test_auto_parse_stock_item_rejects_hallucinated_alloy() -> None: stock.refresh_from_db() assert stock.metal_type == "aluminium" - assert stock.alloy == "" + assert stock.alloy is None assert stock.specifics == "Sheet" @@ -299,9 +299,9 @@ def test_auto_parse_stock_item_rejects_hallucinated_alloy() -> None: def test_auto_parse_stock_item_rejects_invalid_metal_type() -> None: stock = _stock( description="Round Plug 55mm", - metal_type="unspecified", - alloy="", - specifics="", + metal_type=None, + alloy=None, + specifics=None, ) parsed_data = { "metal_type": "plastic", @@ -318,9 +318,9 @@ def test_auto_parse_stock_item_rejects_invalid_metal_type() -> None: stock.refresh_from_db() assert stock.parser_attempted_at is not None assert stock.parsed_at is None - assert stock.metal_type == "unspecified" - assert stock.alloy == "" - assert stock.specifics == "" + assert stock.metal_type is None + assert stock.alloy is None + assert stock.specifics is None @pytest.mark.django_db diff --git a/apps/purchasing/tests/test_supplier_search.py b/apps/purchasing/tests/test_supplier_search.py index f1a91b687..7dd67115c 100644 --- a/apps/purchasing/tests/test_supplier_search.py +++ b/apps/purchasing/tests/test_supplier_search.py @@ -65,13 +65,13 @@ def auth_api(db): @pytest.mark.django_db -def test_s_and_t_matches_s_t_supplier_name(): +def test_s_and_t_matches_s_t_supplier_name() -> None: _make_client("S&T Stainless Limited") assert _names("S&T")[0] == "S&T Stainless Limited" -def test_s_and_t_scores_substring_match_above_reversed_initials(): +def test_s_and_t_scores_substring_match_above_reversed_initials() -> None: assert _supplier_name_score( "S&T Stainless Limited", "S&T", @@ -82,7 +82,7 @@ def test_s_and_t_scores_substring_match_above_reversed_initials(): @pytest.mark.django_db -def test_s_and_t_prefers_s_t_supplier_after_broad_single_letter_matches(): +def test_s_and_t_prefers_s_t_supplier_after_broad_single_letter_matches() -> None: for index in range(550): _make_client(f"Stainless Test Distractor {index:03d}") @@ -101,7 +101,7 @@ def test_s_and_t_prefers_s_t_supplier_after_broad_single_letter_matches(): @pytest.mark.django_db -def test_s_and_t_ranks_s_t_before_t_s(): +def test_s_and_t_ranks_s_t_before_t_s() -> None: _make_client("T&S Stainless Ranking Test") _make_client("S&T Stainless Ranking Test") @@ -112,7 +112,7 @@ def test_s_and_t_ranks_s_t_before_t_s(): @pytest.mark.django_db -def test_supplier_alias_matches_attached_client(): +def test_supplier_alias_matches_attached_client() -> None: supplier = _make_client("S&T Stainless Limited") SupplierSearchAlias.objects.create(company=supplier, alias="Steel and Tube") @@ -120,14 +120,14 @@ def test_supplier_alias_matches_attached_client(): @pytest.mark.django_db -def test_allow_jobs_false_does_not_exclude_supplier(): +def test_allow_jobs_false_does_not_exclude_supplier() -> None: _make_client("S&T Stainless Limited", allow_jobs=False) assert _names("S&T")[0] == "S&T Stainless Limited" @pytest.mark.django_db -def test_is_supplier_false_can_outrank_is_supplier_true_with_purchase_history(): +def test_is_supplier_false_can_outrank_is_supplier_true_with_purchase_history() -> None: frequent = _make_client("S&T Stainless Limited", is_supplier=False) _make_client("S&T Stainless Alternate", is_supplier=True) _make_po(frequent) @@ -137,7 +137,7 @@ def test_is_supplier_false_can_outrank_is_supplier_true_with_purchase_history(): @pytest.mark.django_db -def test_archived_and_merged_clients_are_excluded(): +def test_archived_and_merged_clients_are_excluded() -> None: active = _make_client("S&T Exclusion Active") _make_client("S&T Exclusion Archived", xero_archived=True) _make_client("S&T Exclusion Merged", merged_into=active) @@ -146,7 +146,7 @@ def test_archived_and_merged_clients_are_excluded(): @pytest.mark.django_db -def test_recent_purchase_history_boosts_but_old_history_does_not(): +def test_recent_purchase_history_boosts_but_old_history_does_not() -> None: recent = _make_client("Steel Supplier Recent") old = _make_client("Steel Supplier Old") _make_po(recent, days_ago=30) @@ -162,7 +162,7 @@ def test_recent_purchase_history_boosts_but_old_history_does_not(): @pytest.mark.django_db -def test_deleted_purchase_orders_do_not_boost_supplier(): +def test_deleted_purchase_orders_do_not_boost_supplier() -> None: active_po_supplier = _make_client("Steel Supplier Active") deleted_po_supplier = _make_client("Steel Supplier Deleted") _make_po(active_po_supplier) @@ -174,7 +174,7 @@ def test_deleted_purchase_orders_do_not_boost_supplier(): @pytest.mark.django_db -def test_stop_word_only_query_falls_back_to_literal_name_match(): +def test_stop_word_only_query_falls_back_to_literal_name_match() -> None: _make_client("The Tool Shed") _make_client("The Metal Company") diff --git a/apps/purchasing/views/purchasing_rest_views.py b/apps/purchasing/views/purchasing_rest_views.py index 0e37b1267..06f4c1776 100644 --- a/apps/purchasing/views/purchasing_rest_views.py +++ b/apps/purchasing/views/purchasing_rest_views.py @@ -15,7 +15,7 @@ from apps.job.models import Job from apps.job.models.costing import CostLine -from apps.purchasing.etag import generate_po_etag, normalize_etag +from apps.purchasing.etag import generate_po_etag from apps.purchasing.models import ( PurchaseOrder, PurchaseOrderEvent, @@ -44,6 +44,7 @@ PurchaseOrderEventsResponseSerializer, PurchaseOrderLastNumberResponseSerializer, PurchaseOrderListSerializer, + PurchaseOrderMutationErrorResponseSerializer, PurchaseOrderPDFResponseSerializer, PurchaseOrderUpdateResponseSerializer, PurchaseOrderUpdateSerializer, @@ -55,7 +56,10 @@ AllocationDeletionError, AllocationService, ) -from apps.purchasing.services.delivery_receipt_service import process_delivery_receipt +from apps.purchasing.services.delivery_receipt_service import ( + DeliveryReceiptValidationError, + process_delivery_receipt, +) from apps.purchasing.services.purchase_order_email_service import ( create_purchase_order_email, ) @@ -63,26 +67,41 @@ create_purchase_order_pdf, ) from apps.purchasing.services.purchasing_rest_service import ( - PreconditionFailedError, PurchasingRestService, ) +from apps.workflow.etag import if_none_match_satisfied +from apps.workflow.exceptions import PreconditionFailedError from apps.workflow.services.error_persistence import persist_app_error +from apps.workflow.services.http_error_service import http_status_for_exception logger = logging.getLogger(__name__) +def _purchase_order_error_response( + exc: Exception, +) -> Response: + """Persist and serialize an exception-derived PO error response.""" + app_error = persist_app_error(exc) + serializer = PurchaseOrderMutationErrorResponseSerializer( + { + "success": False, + "error": str(exc), + "details": {"error_id": app_error.id}, + } + ) + return Response(serializer.data, status=http_status_for_exception(exc)) + + class PurchaseOrderETagMixin: """Shared helpers for purchase order ETag handling.""" def _get_if_match(self, request): - header = request.headers.get("If-Match") or request.META.get("HTTP_IF_MATCH") - return normalize_etag(header) if header else None + return request.headers.get("If-Match") or request.META.get("HTTP_IF_MATCH") def _get_if_none_match(self, request): - header = request.headers.get("If-None-Match") or request.META.get( + return request.headers.get("If-None-Match") or request.META.get( "HTTP_IF_NONE_MATCH" ) - return normalize_etag(header) if header else None def _precondition_required_response(self): error = {"error": "Missing If-Match header (precondition required)"} @@ -459,7 +478,7 @@ def get(self, request, po_id): ) etag = generate_po_etag(po) if_none_match = self._get_if_none_match(request) - if if_none_match and if_none_match == normalize_etag(etag): + if if_none_match and if_none_match_satisfied(if_none_match, etag): return Response(status=status.HTTP_304_NOT_MODIFIED) response = Response(serializer.data) self._set_etag(response, etag) @@ -505,27 +524,17 @@ def patch(self, request, po_id): logger.warning( "ETag mismatch updating purchase order %s: %s", po_id, str(exc) ) - return Response( - {"error": "Precondition failed (ETag mismatch). Reload and retry."}, - status=status.HTTP_412_PRECONDITION_FAILED, - ) + return _purchase_order_error_response(exc) except ValidationError as exc: - persist_app_error(exc) logger.warning( "Validation error updating purchase order %s: %s", po_id, str(exc) ) - return Response( - {"error": "Validation error", "details": str(exc)}, - status=status.HTTP_400_BAD_REQUEST, - ) + return _purchase_order_error_response(exc) - except Exception as e: - logger.error(f"Error updating purchase order {id}: {str(e)}") - return Response( - {"error": "Failed to update purchase order", "details": str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) + except Exception as exc: + logger.exception("Error updating purchase order %s", po_id) + return _purchase_order_error_response(exc) class DeliveryReceiptRestView(PurchaseOrderETagMixin, APIView): @@ -541,7 +550,10 @@ class DeliveryReceiptRestView(PurchaseOrderETagMixin, APIView): @extend_schema( responses={ status.HTTP_200_OK: DeliveryReceiptResponseSerializer, - status.HTTP_400_BAD_REQUEST: DeliveryReceiptResponseSerializer, + status.HTTP_400_BAD_REQUEST: PurchaseOrderMutationErrorResponseSerializer, + status.HTTP_404_NOT_FOUND: PurchaseOrderMutationErrorResponseSerializer, + status.HTTP_412_PRECONDITION_FAILED: PurchaseOrderMutationErrorResponseSerializer, + status.HTTP_500_INTERNAL_SERVER_ERROR: PurchaseOrderMutationErrorResponseSerializer, } ) def post(self, request): @@ -584,23 +596,17 @@ def post(self, request): purchase_order_id, str(exc), ) - response_data = { - "success": False, - "error": "Precondition failed (ETag mismatch). Reload and retry.", - } - response_serializer = DeliveryReceiptResponseSerializer(response_data) - return Response( - response_serializer.data, - status=status.HTTP_412_PRECONDITION_FAILED, - ) + return _purchase_order_error_response(exc) - except Exception as e: - logger.error(f"Error processing delivery receipt: {str(e)}") - response_data = {"success": False, "error": str(e)} - response_serializer = DeliveryReceiptResponseSerializer(response_data) - return Response( - response_serializer.data, status=status.HTTP_400_BAD_REQUEST - ) + except DeliveryReceiptValidationError as exc: + return _purchase_order_error_response(exc) + + except PurchaseOrder.DoesNotExist as exc: + return _purchase_order_error_response(exc) + + except Exception as exc: + logger.exception("Error processing delivery receipt") + return _purchase_order_error_response(exc) class PurchaseOrderAllocationsAPIView(APIView): @@ -723,7 +729,7 @@ def get(self, request, po_id): "allocation_date": stock_item.date.isoformat(), "description": stock_item.description, "stock_location": stock_item.location or "Not specified", - "metal_type": stock_item.metal_type or "unspecified", + "metal_type": stock_item.metal_type, "alloy": stock_item.alloy or "", "specifics": stock_item.specifics or "", "allocation_id": str(stock_item.id), diff --git a/apps/quoting/migrations/0002_forbid_blank_text.py b/apps/quoting/migrations/0002_forbid_blank_text.py new file mode 100644 index 000000000..f8bb2a0f3 --- /dev/null +++ b/apps/quoting/migrations/0002_forbid_blank_text.py @@ -0,0 +1,76 @@ +"""Forbid empty strings in nullable text columns (quoting). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "quoting_productparsingmapping": [ + "derived_key", + "mapped_alloy", + "mapped_description", + "mapped_dimensions", + "mapped_item_code", + "mapped_metal_type", + "mapped_price_unit", + "mapped_specifics", + "parser_version", + "validation_notes" + ], + "quoting_scrapejob": [ + "error_message" + ], + "quoting_suppliercredential": [ + "api_key", + "password", + "username" + ], + "quoting_supplierproduct": [ + "description", + "mapping_hash", + "parsed_alloy", + "parsed_description", + "parsed_dimensions", + "parsed_item_code", + "parsed_metal_type", + "parsed_price_unit", + "parsed_specifics", + "parser_version", + "price_unit", + "specifications", + "variant_length", + "variant_width" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("quoting", "0001_baseline"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/quoting/migrations/0003_text_unset_is_null.py b/apps/quoting/migrations/0003_text_unset_is_null.py new file mode 100644 index 000000000..bb65d65ae --- /dev/null +++ b/apps/quoting/migrations/0003_text_unset_is_null.py @@ -0,0 +1,54 @@ +# Generated by Django 6.0.7 on 2026-07-28 00:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("quoting", "0002_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="productparsingmapping", + name="mapped_metal_type", + field=models.CharField( + blank=True, + choices=[ + ("stainless_steel", "Stainless Steel"), + ("mild_steel", "Mild Steel"), + ("aluminium", "Aluminium"), + ("brass", "Brass"), + ("copper", "Copper"), + ("titanium", "Titanium"), + ("zinc", "Zinc"), + ("galvanized", "Galvanized"), + ("other", "Other"), + ], + max_length=50, + null=True, + ), + ), + migrations.AlterField( + model_name="supplierproduct", + name="parsed_metal_type", + field=models.CharField( + blank=True, + choices=[ + ("stainless_steel", "Stainless Steel"), + ("mild_steel", "Mild Steel"), + ("aluminium", "Aluminium"), + ("brass", "Brass"), + ("copper", "Copper"), + ("titanium", "Titanium"), + ("zinc", "Zinc"), + ("galvanized", "Galvanized"), + ("other", "Other"), + ], + help_text="Metal type parsed from product specifications", + max_length=50, + null=True, + ), + ), + ] diff --git a/apps/quoting/serializers_scheduled_tasks.py b/apps/quoting/serializers_scheduled_tasks.py index 789dd3e8d..a47592f63 100644 --- a/apps/quoting/serializers_scheduled_tasks.py +++ b/apps/quoting/serializers_scheduled_tasks.py @@ -8,8 +8,10 @@ from django_celery_results.models import TaskResult from rest_framework import serializers +from apps.workflow.serializers_base import NullUnsetModelSerializer -class ScheduledTaskSerializer(serializers.ModelSerializer): + +class ScheduledTaskSerializer(NullUnsetModelSerializer[PeriodicTask]): """Read-only view over django-celery-beat's PeriodicTask. Frontend uses: id, name, task, enabled, last_run_at, schedule. @@ -42,7 +44,7 @@ def get_schedule(self, obj: PeriodicTask) -> str: return "" -class ScheduledTaskExecutionSerializer(serializers.ModelSerializer): +class ScheduledTaskExecutionSerializer(NullUnsetModelSerializer[TaskResult]): """Read-only view over django-celery-results' TaskResult. `task_name` here is the dotted Celery task name (e.g. diff --git a/apps/quoting/services/ai_price_extraction.py b/apps/quoting/services/ai_price_extraction.py index df1dee301..19517fde1 100644 --- a/apps/quoting/services/ai_price_extraction.py +++ b/apps/quoting/services/ai_price_extraction.py @@ -47,9 +47,7 @@ def get_prioritized_active_providers(): """ from apps.workflow.models import AIProvider - active_providers = AIProvider.objects.filter(api_key__isnull=False).exclude( - api_key="" - ) + active_providers = AIProvider.objects.filter(api_key__isnull=False) # Define priority order (lower number = higher priority) priority_map = { diff --git a/apps/quoting/services/stock_parser.py b/apps/quoting/services/stock_parser.py index c497767dd..105b73dc9 100644 --- a/apps/quoting/services/stock_parser.py +++ b/apps/quoting/services/stock_parser.py @@ -135,10 +135,7 @@ def auto_parse_stock_item(stock_instance: Stock, *, force: bool = False) -> None parsed_metal_type = _normalise_metal_type(parsed_data.get("metal_type")) parsed_alloy = _normalise_alloy(parsed_data.get("alloy"), stock_instance) - if ( - not stock_instance.metal_type - or stock_instance.metal_type == "unspecified" - ): + if not stock_instance.metal_type: if parsed_metal_type: updates["metal_type"] = parsed_metal_type accepted_metadata_fields.append("metal_type") @@ -157,10 +154,7 @@ def auto_parse_stock_item(stock_instance: Stock, *, force: bool = False) -> None pass # Existing alloy wins over parser output. if not stock_instance.specifics: - has_existing_metal_type = bool( - stock_instance.metal_type - and stock_instance.metal_type != "unspecified" - ) + has_existing_metal_type = bool(stock_instance.metal_type) has_material_context = bool( stock_instance.alloy or has_existing_metal_type diff --git a/apps/quoting/tests/test_pdf_data_validation.py b/apps/quoting/tests/test_pdf_data_validation.py index 013b2e028..7e76097dc 100644 --- a/apps/quoting/tests/test_pdf_data_validation.py +++ b/apps/quoting/tests/test_pdf_data_validation.py @@ -7,7 +7,7 @@ class PDFDataValidationServiceTest(BaseTestCase): """Test cases for PDFDataValidationService.""" - def setUp(self): + def setUp(self) -> None: """Set up test data.""" self.service = PDFDataValidationService() @@ -23,7 +23,7 @@ def setUp(self): supplier=self.supplier, file_name="test_price_list.pdf" ) - def test_validate_extracted_data_valid(self): + def test_validate_extracted_data_valid(self) -> None: """Test validation of valid extracted data.""" data = { "supplier": {"name": "Test Supplier"}, @@ -44,7 +44,7 @@ def test_validate_extracted_data_valid(self): self.assertEqual(len(errors), 0) self.assertEqual(len(warnings), 0) - def test_validate_extracted_data_missing_supplier(self): + def test_validate_extracted_data_missing_supplier(self) -> None: """Test validation with missing supplier name.""" data = { "supplier": {}, @@ -58,7 +58,7 @@ def test_validate_extracted_data_missing_supplier(self): self.assertFalse(is_valid) self.assertIn("Missing supplier name", errors) - def test_validate_extracted_data_no_items(self): + def test_validate_extracted_data_no_items(self) -> None: """Test validation with no items.""" data = {"supplier": {"name": "Test Supplier"}, "items": []} @@ -67,7 +67,7 @@ def test_validate_extracted_data_no_items(self): self.assertTrue(is_valid) # No items is valid, just a warning self.assertIn("No items found in extracted data", warnings) - def test_validate_extracted_data_invalid_items(self): + def test_validate_extracted_data_invalid_items(self) -> None: """Test validation with invalid items.""" data = { "supplier": {"name": "Test Supplier"}, @@ -84,7 +84,7 @@ def test_validate_extracted_data_invalid_items(self): self.assertFalse(is_valid) self.assertIn("No valid items found", errors) - def test_normalize_price_valid_formats(self): + def test_normalize_price_valid_formats(self) -> None: """Test price normalization with various valid formats.""" test_cases = [ ("12.50", 12.50), @@ -104,7 +104,7 @@ def test_normalize_price_valid_formats(self): result = self.service._normalize_price(input_price) self.assertEqual(result, expected) - def test_normalize_price_invalid_formats(self): + def test_normalize_price_invalid_formats(self) -> None: """Test price normalization with invalid formats.""" invalid_prices = ["abc", "12.50.75", "$", "not-a-number"] @@ -113,7 +113,7 @@ def test_normalize_price_invalid_formats(self): with self.assertRaises(ValueError): self.service._normalize_price(invalid_price) - def test_clean_text(self): + def test_clean_text(self) -> None: """Test text cleaning functionality.""" test_cases = [ (" Normal text ", "Normal text"), @@ -130,7 +130,7 @@ def test_clean_text(self): result = self.service._clean_text(input_text) self.assertEqual(result, expected) - def test_sanitize_product_data(self): + def test_sanitize_product_data(self) -> None: """Test product data sanitization.""" products = [ { @@ -164,7 +164,7 @@ def test_sanitize_product_data(self): self.assertEqual(sanitized[1]["product_name"], "Steel Bar") self.assertEqual(sanitized[1]["unit_price"], 15.75) - def test_check_duplicates_no_existing_supplier(self): + def test_check_duplicates_no_existing_supplier(self) -> None: """Test duplicate checking when supplier doesn't exist.""" products = [{"product_name": "New Product", "item_no": "NEW-001"}] @@ -173,7 +173,7 @@ def test_check_duplicates_no_existing_supplier(self): self.assertEqual(len(result["duplicates"]), 0) self.assertEqual(len(result["new"]), 1) - def test_check_duplicates_with_existing_products(self): + def test_check_duplicates_with_existing_products(self) -> None: """Test duplicate checking with existing products.""" # Create existing product SupplierProduct.objects.create( @@ -203,7 +203,7 @@ def test_check_duplicates_with_existing_products(self): self.assertEqual(len(result["new"]), 1) self.assertEqual(result["new"][0]["product_name"], "New Product") - def test_validation_summary(self): + def test_validation_summary(self) -> None: """Test validation summary generation.""" # Run validation that will generate errors and warnings data = { @@ -224,7 +224,7 @@ def test_validation_summary(self): self.assertEqual(summary["warning_count"], 1) self.assertIn("Missing supplier name", summary["errors"]) - def test_long_field_truncation(self): + def test_long_field_truncation(self) -> None: """Test that long fields are properly truncated.""" long_name = "A" * 600 # Longer than 500 char limit long_item_no = "B" * 150 # Longer than 100 char limit @@ -242,7 +242,7 @@ def test_long_field_truncation(self): self.assertEqual(len(sanitized[0]["product_name"]), 500) self.assertEqual(len(sanitized[0]["item_no"]), 100) - def test_variant_id_generation(self): + def test_variant_id_generation(self) -> None: """Test automatic variant_id generation when missing.""" products = [ {"product_name": "Product 1", "description": "First product"}, diff --git a/apps/timesheet/serializers/modern_timesheet_serializers.py b/apps/timesheet/serializers/modern_timesheet_serializers.py index 6d8a1655d..f8b9dede7 100644 --- a/apps/timesheet/serializers/modern_timesheet_serializers.py +++ b/apps/timesheet/serializers/modern_timesheet_serializers.py @@ -14,9 +14,10 @@ from apps.timesheet.serializers.daily_timesheet_serializers import ( SummaryStatsSerializer, ) +from apps.workflow.serializers_base import NullUnsetModelSerializer -class ModernTimesheetJobSerializer(serializers.ModelSerializer): +class ModernTimesheetJobSerializer(NullUnsetModelSerializer[Job]): """Serializer for jobs in timesheet context using modern CostSet system""" company_name = serializers.CharField( diff --git a/apps/timesheet/tests/test_daily_timesheet_service.py b/apps/timesheet/tests/test_daily_timesheet_service.py index 0d2d1c5b8..caf25a89a 100644 --- a/apps/timesheet/tests/test_daily_timesheet_service.py +++ b/apps/timesheet/tests/test_daily_timesheet_service.py @@ -13,7 +13,7 @@ class DailyTimesheetServiceTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.target_date = date(2026, 5, 22) self.company = Company.objects.create( @@ -87,7 +87,9 @@ def _create_time_line(self, job: Job, *, hours: str, entry_seq: int) -> CostLine entry_seq=entry_seq, ) - def test_staff_timesheet_data_does_not_lazy_load_job_breakdown_relations(self): + def test_staff_timesheet_data_does_not_lazy_load_job_breakdown_relations( + self, + ) -> None: """Catches N+1 DB access while daily timesheets serialize job/company data.""" other_job = self._create_job( job_number=98767, diff --git a/apps/timesheet/tests/test_pay_run_list_api.py b/apps/timesheet/tests/test_pay_run_list_api.py index 90472a26d..a17bba099 100644 --- a/apps/timesheet/tests/test_pay_run_list_api.py +++ b/apps/timesheet/tests/test_pay_run_list_api.py @@ -1,6 +1,6 @@ import uuid from datetime import date, datetime, timezone -from unittest.mock import patch +from unittest.mock import MagicMock, patch from django.urls import reverse from rest_framework.test import APIClient @@ -11,7 +11,7 @@ class PayRunListApiTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.client_api = APIClient() self.superuser = Staff.objects.create_user( @@ -30,7 +30,10 @@ def setUp(self): self.calendar_id = company.xero_payroll_calendar_id @patch("apps.timesheet.views.api.build_xero_payroll_url") - def test_list_pay_runs_uses_local_mirror_table(self, mock_build_xero_payroll_url): + def test_list_pay_runs_uses_local_mirror_table( + self, + mock_build_xero_payroll_url: MagicMock, + ) -> None: mock_build_xero_payroll_url.return_value = "https://example.test/payrun/1" pay_run = XeroPayRun.objects.create( xero_id=uuid.uuid4(), @@ -57,8 +60,9 @@ def test_list_pay_runs_uses_local_mirror_table(self, mock_build_xero_payroll_url @patch("apps.timesheet.views.api.build_xero_payroll_url") def test_next_postable_week_is_after_latest_run_when_no_draft( - self, mock_build_xero_payroll_url - ): + self, + mock_build_xero_payroll_url: MagicMock, + ) -> None: mock_build_xero_payroll_url.return_value = "https://example.test/payrun/1" XeroPayRun.objects.create( xero_id=uuid.uuid4(), @@ -82,8 +86,9 @@ def test_next_postable_week_is_after_latest_run_when_no_draft( @patch("apps.workflow.api.xero.payroll.get_payroll_calendars") def test_next_postable_week_uses_calendar_anchor_when_no_pay_runs( - self, mock_get_payroll_calendars - ): + self, + mock_get_payroll_calendars: MagicMock, + ) -> None: mock_get_payroll_calendars.return_value = [ { "id": str(self.calendar_id), diff --git a/apps/timesheet/tests/test_payroll_post_api.py b/apps/timesheet/tests/test_payroll_post_api.py index ea5db51db..e1e75b933 100644 --- a/apps/timesheet/tests/test_payroll_post_api.py +++ b/apps/timesheet/tests/test_payroll_post_api.py @@ -13,7 +13,7 @@ class PayrollPostStartApiTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.client_api = APIClient() self.superuser = Staff.objects.create_user( @@ -243,7 +243,7 @@ def test_stream_skips_staff_who_left_before_posted_week( class WeeklyTimesheetApiContractTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() self.client_api = APIClient() self.superuser = Staff.objects.create_user( @@ -256,7 +256,7 @@ def setUp(self): ) self.client_api.force_authenticate(user=self.superuser) - def test_weekly_api_returns_five_days_when_weekends_disabled(self): + def test_weekly_api_returns_five_days_when_weekends_disabled(self) -> None: company = CompanyDefaults.get_solo() company.weekend_timesheets_enabled = False company.save(update_fields=["weekend_timesheets_enabled"]) @@ -274,7 +274,7 @@ def test_weekly_api_returns_five_days_when_weekends_disabled(self): self.assertEqual(payload["start_date"], "2026-05-04") self.assertEqual(payload["end_date"], "2026-05-08") - def test_weekly_api_returns_seven_days_when_weekends_enabled(self): + def test_weekly_api_returns_seven_days_when_weekends_enabled(self) -> None: company = CompanyDefaults.get_solo() company.weekend_timesheets_enabled = True company.save(update_fields=["weekend_timesheets_enabled"]) diff --git a/apps/timesheet/tests/test_permissions.py b/apps/timesheet/tests/test_permissions.py index 8646f247c..492f88854 100644 --- a/apps/timesheet/tests/test_permissions.py +++ b/apps/timesheet/tests/test_permissions.py @@ -11,7 +11,7 @@ class TimesheetPermissionTests(BaseTestCase): """Test that timesheet endpoints require superuser access.""" - def setUp(self): + def setUp(self) -> None: self.factory = RequestFactory() self.client_api = APIClient() @@ -32,39 +32,39 @@ def setUp(self): is_office_staff=True, ) - def test_weekly_timesheet_blocked_for_normal_user(self): + def test_weekly_timesheet_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) response = self.client_api.get("/api/timesheets/weekly/") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_weekly_timesheet_allowed_for_superuser(self): + def test_weekly_timesheet_allowed_for_superuser(self) -> None: self.client_api.force_authenticate(user=self.superuser) response = self.client_api.get("/api/timesheets/weekly/") self.assertNotEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_daily_summary_blocked_for_normal_user(self): + def test_daily_summary_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) response = self.client_api.get("/api/timesheets/daily/2026-04-01/") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_staff_daily_detail_blocked_for_normal_user(self): + def test_staff_daily_detail_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) response = self.client_api.get( f"/api/timesheets/staff/{self.superuser.id}/daily/2026-04-01/" ) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_staff_list_blocked_for_normal_user(self): + def test_staff_list_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) response = self.client_api.get("/api/timesheets/staff/") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_jobs_list_blocked_for_normal_user(self): + def test_jobs_list_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) response = self.client_api.get("/api/timesheets/jobs/") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_payroll_endpoints_blocked_for_normal_user(self): + def test_payroll_endpoints_blocked_for_normal_user(self) -> None: self.client_api.force_authenticate(user=self.normal_user) # Post week to Xero response = self.client_api.post("/api/timesheets/payroll/post-staff-week/") @@ -79,7 +79,7 @@ def test_payroll_endpoints_blocked_for_normal_user(self): response = self.client_api.get("/api/timesheets/payroll/pay-runs/") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_stream_payroll_blocked_for_normal_user(self): + def test_stream_payroll_blocked_for_normal_user(self) -> None: # stream_payroll_post is a plain Django view, not DRF — use session auth self.client.force_login(self.normal_user) response = self.client.get( diff --git a/apps/timesheet/tests/test_weekly_timesheet_service.py b/apps/timesheet/tests/test_weekly_timesheet_service.py index ef0600c03..8c78ca529 100644 --- a/apps/timesheet/tests/test_weekly_timesheet_service.py +++ b/apps/timesheet/tests/test_weekly_timesheet_service.py @@ -12,7 +12,7 @@ class WeeklyTimesheetServiceCostTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: super().setUp() company = CompanyDefaults.get_solo() company.annual_leave_loading = Decimal("20.00") @@ -47,7 +47,7 @@ def setUp(self): ) self.job.labour_rates.update(charge_out_rate=Decimal("120.00")) - def test_weekly_cash_and_loaded_costs_use_annual_leave_loading(self): + def test_weekly_cash_and_loaded_costs_use_annual_leave_loading(self) -> None: week_start = date(2025, 5, 5) for day_offset in range(5): CostLine.objects.create( diff --git a/apps/workflow/__init__.py b/apps/workflow/__init__.py index 405847d8b..066fa7884 100644 --- a/apps/workflow/__init__.py +++ b/apps/workflow/__init__.py @@ -4,6 +4,7 @@ from .enums import AIProviderTypes, NotebookLmRestriction from .exceptions import ( NoValidXeroTokenError, + PreconditionFailedError, XeroQuotaFloorReached, XeroSyncAlreadyRunningError, XeroValidationError, @@ -20,6 +21,12 @@ service_api_key_required, ) from .context_processors import debug_mode + from .etag import ( + generate_updated_at_etag, + if_match_satisfied, + if_none_match_satisfied, + updated_at_etag_value, + ) from .exception_handlers import custom_exception_handler from .extensions import CookieJWTScheme from .middleware import ( @@ -28,6 +35,7 @@ FrontendRedirectMiddleware, LoginRequiredMiddleware, PasswordStrengthMiddleware, + ResourceVersionMiddleware, ) from .permissions import F from .search_telemetry_serializers import ( @@ -38,7 +46,9 @@ AIProviderCreateUpdateSerializer, AIProviderSerializer, AppErrorDetailResponseSerializer, + AppErrorDetailsSerializer, AppErrorListResponseSerializer, + AppErrorResponseSerializer, AppErrorSerializer, CompanyDefaultsSchemaSerializer, CompanyDefaultsSerializer, @@ -77,6 +87,7 @@ XeroSyncStartResponseSerializer, XeroTriggerSyncResponseSerializer, ) + from .serializers_base import NullUnsetModelSerializer from .tasks import ( celery_health_check, process_xero_webhook_event, @@ -104,7 +115,9 @@ "AIProviderTypes", "AccessLoggingMiddleware", "AppErrorDetailResponseSerializer", + "AppErrorDetailsSerializer", "AppErrorListResponseSerializer", + "AppErrorResponseSerializer", "AppErrorSerializer", "CompanyDefaultsSchemaSerializer", "CompanyDefaultsSerializer", @@ -121,7 +134,10 @@ "NoValidXeroTokenError", "NotebookLmLinkSerializer", "NotebookLmRestriction", + "NullUnsetModelSerializer", "PasswordStrengthMiddleware", + "PreconditionFailedError", + "ResourceVersionMiddleware", "SearchTelemetryClickRequestSerializer", "SearchTelemetryClickResponseSerializer", "ServiceAPIKeyAuthentication", @@ -165,13 +181,17 @@ "custom_exception_handler", "debug_mode", "extract_messages", + "generate_updated_at_etag", "get_machine_id", + "if_match_satisfied", + "if_none_match_satisfied", "is_valid_invoice_number", "is_valid_uuid", "parse_pagination_params", "process_xero_webhook_event", "purge_old_session_replays_task", "service_api_key_required", + "updated_at_etag_value", "validate_webhook_signature", "xero_30_day_sync_task", "xero_heartbeat_task", diff --git a/apps/workflow/accounting/xero/readonly_provider.py b/apps/workflow/accounting/xero/readonly_provider.py index 82485f537..cb368174c 100644 --- a/apps/workflow/accounting/xero/readonly_provider.py +++ b/apps/workflow/accounting/xero/readonly_provider.py @@ -34,7 +34,6 @@ logger = logging.getLogger("xero") -_GST_RATE = Decimal("0.15") _CENT = Decimal("0.01") @@ -48,10 +47,12 @@ def _log_suppressed(operation: str, detail: str) -> None: def _fake_totals(line_items: list[DocumentLineItem]) -> tuple[str, str, str]: """Cosmetic GST-exclusive totals for stubbed documents (local display only).""" + from apps.workflow.models import CompanyDefaults + sub_total = sum( (li.quantity * li.unit_amount for li in line_items), Decimal("0") ).quantize(_CENT) - tax = (sub_total * _GST_RATE).quantize(_CENT) + tax = (sub_total * CompanyDefaults.get_solo().gst_rate).quantize(_CENT) total = sub_total + tax return str(sub_total), str(tax), str(total) diff --git a/apps/workflow/api/xero/reprocess_xero.py b/apps/workflow/api/xero/reprocess_xero.py index cfd6ce6c7..7e4701f3e 100644 --- a/apps/workflow/api/xero/reprocess_xero.py +++ b/apps/workflow/api/xero/reprocess_xero.py @@ -270,7 +270,8 @@ def set_company_fields(company: Company, new_from_xero: bool = False) -> None: company.name = raw_json.get("_name", company.name or "Unnamed Company") # This is the general email for the contact/company - company.email = raw_json.get("_email_address", company.email) + # Xero sends "" for contacts with no email; NULL is our only unset. + company.email = raw_json.get("_email_address", company.email) or None # Update xero_contact_id from raw_json if available # This ensures the link to the Xero contact is maintained or established. @@ -322,9 +323,8 @@ def set_company_fields(company: Company, new_from_xero: bool = False) -> None: ] street_address = ", ".join(filter(None, parts)) break # Found street address - company.address = ( - street_address or company.address - ) # Use street_address if found, else keep existing or empty + # Use street_address if found, else keep existing; "" is never stored. + company.address = street_address or company.address or None # Create SupplierPickupAddress from Xero STREET address for any company if isinstance(raw_json.get("_addresses"), list): diff --git a/apps/workflow/api/xero/stock_sync.py b/apps/workflow/api/xero/stock_sync.py index ded710e09..811fe252b 100644 --- a/apps/workflow/api/xero/stock_sync.py +++ b/apps/workflow/api/xero/stock_sync.py @@ -76,10 +76,9 @@ def generate_item_code(stock_item: Stock) -> str: "zinc": "ZN", "galvanised": "GAL", "other": "OT", - "unspecified": "UN", } - metal_prefix = metal_map.get(stock_item.metal_type, "UN") + metal_prefix = metal_map.get(stock_item.metal_type or "", "UN") parts.append(metal_prefix) # Add alloy if available @@ -562,7 +561,6 @@ def update_stock_item_codes(): # Find items with missing codes OR codes that are too long (>30 chars) stock_items = Stock.objects.filter( models.Q(item_code__isnull=True) - | models.Q(item_code="") | models.Q(item_code__regex=r"^.{31,}$"), # More than 30 characters is_active=True, ) diff --git a/apps/workflow/api/xero/sync.py b/apps/workflow/api/xero/sync.py index 0e8bf3184..c028961dd 100644 --- a/apps/workflow/api/xero/sync.py +++ b/apps/workflow/api/xero/sync.py @@ -160,7 +160,7 @@ def sync_xero_data( pagination_mode="single", xero_tenant_id=None, entity_key=None, -): +) -> Iterator[XeroSyncEvent]: """Sync data from Xero with pagination support. Args: diff --git a/apps/workflow/api/xero/transforms.py b/apps/workflow/api/xero/transforms.py index 4d12e43cb..e9cc92769 100644 --- a/apps/workflow/api/xero/transforms.py +++ b/apps/workflow/api/xero/transforms.py @@ -1,5 +1,6 @@ import logging import time +from collections.abc import Iterable from datetime import date, datetime from datetime import timezone as dt_timezone from decimal import Decimal @@ -7,6 +8,7 @@ from django.utils import timezone from xero_python.accounting import AccountingApi +from xero_python.accounting.models import Account from apps.accounting.models import Bill, CreditNote, Invoice, Quote from apps.company.models import Company @@ -999,20 +1001,18 @@ def sync_companies(xero_contacts): return companies -def sync_accounts(xero_accounts): +def sync_accounts(xero_accounts: Iterable[Account]) -> None: """Sync Xero accounts""" for account in xero_accounts: XeroAccount.objects.update_or_create( xero_id=account.account_id, defaults={ - "account_code": account.code, + "account_code": account.code or None, "account_name": account.name, - "description": getattr(account, "description", None), - "account_type": account.type, - "tax_type": account.tax_type, - "enable_payments": getattr( - account, "enable_payments_to_account", False - ), + "description": account.description or None, + "account_type": account.type or None, + "tax_type": account.tax_type or None, + "enable_payments": account.enable_payments_to_account, "xero_last_modified": account._updated_date_utc, "xero_last_synced": timezone.now(), "raw_json": process_xero_data(account), diff --git a/apps/workflow/etag.py b/apps/workflow/etag.py new file mode 100644 index 000000000..5354ce761 --- /dev/null +++ b/apps/workflow/etag.py @@ -0,0 +1,50 @@ +"""Shared HTTP ETag helpers for updated-at versioned resources.""" + +from datetime import UTC, datetime +from uuid import UUID + +from django.utils import timezone +from django.utils.http import parse_etags, quote_etag + + +def updated_at_etag_value( + resource: str, + resource_id: UUID, + updated_at: datetime, +) -> str: + """Return the opaque value for a full-precision resource ETag.""" + if timezone.is_naive(updated_at): + raise ValueError("ETag timestamps must be timezone-aware") + + timestamp = ( + updated_at.astimezone(UTC) + .isoformat(timespec="microseconds") + .replace("+00:00", "Z") + ) + return f"{resource}:{resource_id}:{timestamp}" + + +def generate_updated_at_etag( + resource: str, + resource_id: UUID, + updated_at: datetime, +) -> str: + """Return a strong ETag suitable for both If-Match and If-None-Match.""" + return quote_etag(updated_at_etag_value(resource, resource_id, updated_at)) + + +def if_match_satisfied(header: str, current_etag: str) -> bool: + """Apply the application's exact-current-version mutation contract.""" + return current_etag in parse_etags(header) + + +def if_none_match_satisfied(header: str, current_etag: str) -> bool: + """Apply RFC weak comparison semantics for conditional GET requests.""" + candidates = parse_etags(header) + if "*" in candidates: + return True + + current_weak_value = current_etag.removeprefix("W/") + return any( + candidate.removeprefix("W/") == current_weak_value for candidate in candidates + ) diff --git a/apps/workflow/exceptions.py b/apps/workflow/exceptions.py index d081ec4c2..ec2648aa0 100644 --- a/apps/workflow/exceptions.py +++ b/apps/workflow/exceptions.py @@ -1,6 +1,10 @@ from typing import List, Optional +class PreconditionFailedError(Exception): + """Raised when an optimistic-concurrency precondition is not met.""" + + class XeroValidationError(Exception): """Exception raised when a Xero object is missing required fields. diff --git a/apps/workflow/fixtures/initial_data.json b/apps/workflow/fixtures/initial_data.json index 75219be6d..4cd6cd1d7 100644 --- a/apps/workflow/fixtures/initial_data.json +++ b/apps/workflow/fixtures/initial_data.json @@ -261,7 +261,7 @@ "label": "Main line", "endpoint_type": "main_line", "staff": null, - "provider_account_code": "", + "provider_account_code": null, "provider_metadata": {}, "is_active": true, "created_at": "2024-01-01T00:00:00Z", diff --git a/apps/workflow/management/commands/seed_xero_from_database.py b/apps/workflow/management/commands/seed_xero_from_database.py index a21b39ac5..9debe6573 100644 --- a/apps/workflow/management/commands/seed_xero_from_database.py +++ b/apps/workflow/management/commands/seed_xero_from_database.py @@ -178,7 +178,7 @@ def handle(self, *args, **options): CompanyDefaults.set_xero_sync_enabled(enabled=True) self.stdout.write("Xero seeding complete! enable_xero_sync is now True.") - def process_accounts(self, dry_run): + def process_accounts(self, dry_run: bool) -> int: """Phase 0: Update XeroAccount xero_ids from prod to dev Xero tenant. The backup includes XeroAccount records with prod xero_id values. @@ -210,13 +210,11 @@ def process_accounts(self, dry_run): account_name=account.name, defaults={ "xero_id": account.account_id, - "account_code": account.code, - "description": getattr(account, "description", None), - "account_type": account.type, - "tax_type": account.tax_type, - "enable_payments": getattr( - account, "enable_payments_to_account", False - ), + "account_code": account.code or None, + "description": account.description or None, + "account_type": account.type or None, + "tax_type": account.tax_type or None, + "enable_payments": account.enable_payments_to_account, "xero_last_modified": account._updated_date_utc, "xero_last_synced": None, "raw_json": process_xero_data(account), diff --git a/apps/workflow/management/commands/xero.py b/apps/workflow/management/commands/xero.py index 9d27414e1..741fa376a 100644 --- a/apps/workflow/management/commands/xero.py +++ b/apps/workflow/management/commands/xero.py @@ -931,9 +931,7 @@ def create_staff(self, options): return # Check for already-linked staff - already_linked = queryset.exclude(xero_user_id__isnull=True).exclude( - xero_user_id="" - ) + already_linked = queryset.exclude(xero_user_id__isnull=True) if already_linked.exists(): self.stdout.write( self.style.WARNING( @@ -944,7 +942,7 @@ def create_staff(self, options): self.stdout.write(f" {s.email} -> {s.xero_user_id}") # Only process unlinked staff - unlinked = queryset.filter(Q(xero_user_id__isnull=True) | Q(xero_user_id="")) + unlinked = queryset.filter(xero_user_id__isnull=True) if not unlinked.exists(): self.stdout.write(self.style.WARNING("No unlinked staff to create.")) diff --git a/apps/workflow/middleware.py b/apps/workflow/middleware.py index 9bffbf0de..c72f9dcad 100644 --- a/apps/workflow/middleware.py +++ b/apps/workflow/middleware.py @@ -17,6 +17,27 @@ access_logger = logging.getLogger("access") +class ResourceVersionMiddleware: + """Preserve strong OCC tokens when gzip weakens representation ETags.""" + + _RESOURCE_ETAG_PREFIXES = ('"job:', '"po:') + + def __init__( + self, + get_response: Callable[[HttpRequest], HttpResponse], + ) -> None: + self.get_response = get_response + + def __call__(self, request: HttpRequest) -> HttpResponse: + response = self.get_response(request) + etag = response.headers.get("ETag") + if etag is None or not etag.startswith(self._RESOURCE_ETAG_PREFIXES): + return response + + response.headers["X-Resource-Version"] = etag + return response + + class DisallowedHostMiddleware: """ Middleware to catch DisallowedHost exceptions and return a clean 400 response. diff --git a/apps/workflow/migrations/0015_normalise_blank_text_to_null.py b/apps/workflow/migrations/0015_normalise_blank_text_to_null.py new file mode 100644 index 000000000..ca71ec1e5 --- /dev/null +++ b/apps/workflow/migrations/0015_normalise_blank_text_to_null.py @@ -0,0 +1,31 @@ +"""Collapse empty-string text values to NULL on nullable workflow fields. + +These columns are all `null=True, blank=True`, so "unset" had two +representations and consumers had to test for each. NULL is the single +unset value; the serializers now reject "" so it cannot come back. + +Irreversible: reverse cannot distinguish rows that were "" from rows that +were already NULL, so it is a no-op rather than a wrong restore. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "workflow_xeroaccount": ["description"], + "workflow_apperror": ["file", "function"], +} + + +class Migration(migrations.Migration): + dependencies = [ + ("workflow", "0014_companydefaults_xero_quote_terms"), + ] + + operations = [ + migrations.RunSQL( + sql=f"UPDATE {table} SET {col} = NULL WHERE {col} = ''", + reverse_sql=migrations.RunSQL.noop, + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/workflow/migrations/0016_forbid_blank_text.py b/apps/workflow/migrations/0016_forbid_blank_text.py new file mode 100644 index 000000000..b9b96d1f6 --- /dev/null +++ b/apps/workflow/migrations/0016_forbid_blank_text.py @@ -0,0 +1,96 @@ +"""Forbid empty strings in nullable text columns (workflow). + +NULL is this codebase's single representation of "unset" for text columns +(see CLAUDE.md). A CHECK constraint is the only guard nothing can bypass: the +API rejects "" at the serializer, but the Django admin, management +commands, the Xero sync and raw SQL all write straight past that. + +Only columns physically on each table are listed: multi-table-inheritance +children (XeroError) store their inherited columns on the parent table, and +constraining them here would target a column the child table does not have. + +Constraint names are unqualified because CHECK constraint names only need +to be unique within their table, and the qualified form overran Postgres's +63-character identifier limit. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "workflow_aiprovider": [ + "api_key" + ], + "workflow_apperror": [ + "app", + "file", + "function" + ], + "workflow_companydefaults": [ + "address_line1", + "address_line2", + "city", + "company_acronym", + "company_email", + "company_url", + "gdrive_how_we_work_folder_id", + "gdrive_quotes_folder_id", + "gdrive_quotes_folder_url", + "gdrive_reference_library_folder_id", + "gdrive_sops_folder_id", + "google_shared_drive_id", + "master_quote_template_id", + "master_quote_template_url", + "post_code", + "suburb", + "test_company_name", + "xero_shortcode", + "xero_tenant_id" + ], + "workflow_searchtelemetryevent": [ + "source_event_hash" + ], + "workflow_xeroaccount": [ + "account_code", + "account_type", + "description", + "tax_type", + "xero_tenant_id" + ], + "workflow_xeroapp": [ + "access_token", + "refresh_token", + "scope", + "token_type" + ], + "workflow_xeropayitem": [ + "xero_id", + "xero_tenant_id" + ], + "workflow_xeropayrun": [ + "pay_run_status", + "pay_run_type" + ], + "workflow_xeropayslip": [ + "employee_name" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("workflow", "0015_normalise_blank_text_to_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f"ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank " + f"CHECK ({col} <> '')" + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/workflow/migrations/0017_text_unset_is_null.py b/apps/workflow/migrations/0017_text_unset_is_null.py new file mode 100644 index 000000000..fac64cbcc --- /dev/null +++ b/apps/workflow/migrations/0017_text_unset_is_null.py @@ -0,0 +1,100 @@ +"""Give workflow's remaining text columns a single spelling of "unset". + +These columns were NOT NULL with blank=True, so "" was their empty value +while their nullable siblings used NULL — the same fact spelled two ways +across the schema. They become nullable, their "" rows become NULL, and a +CHECK constraint keeps them that way (see CLAUDE.md). + +Irreversible: reverse cannot tell a row that was "" from one that was +already NULL, so it is a no-op rather than a wrong restore. +""" + + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("workflow", "0016_forbid_blank_text"), + ] + + operations = [ + migrations.AlterField( + model_name="aiprovider", + name="model_name", + field=models.CharField( + blank=True, + help_text="Model name (e.g., gemini-flash-latest)", + max_length=100, + null=True, + ), + ), + migrations.AlterField( + model_name="searchtelemetryevent", + name="normalized_query", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="searchtelemetryevent", + name="query", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="searchtelemetryevent", + name="selected_label", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="searchtelemetryevent", + name="selected_result_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="searchtelemetryevent", + name="source", + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name="sessionreplayrecording", + name="user_agent", + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name="xeroapp", + name="webhook_key", + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.RunSQL( + sql='UPDATE workflow_aiprovider SET "model_name" = NULL WHERE "model_name" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_searchtelemetryevent SET "source" = NULL WHERE "source" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_searchtelemetryevent SET "query" = NULL WHERE "query" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_searchtelemetryevent SET "normalized_query" = NULL WHERE "normalized_query" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_searchtelemetryevent SET "selected_result_id" = NULL WHERE "selected_result_id" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_searchtelemetryevent SET "selected_label" = NULL WHERE "selected_label" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_sessionreplayrecording SET "user_agent" = NULL WHERE "user_agent" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RunSQL( + sql='UPDATE workflow_xeroapp SET "webhook_key" = NULL WHERE "webhook_key" = \'\'', + reverse_sql=migrations.RunSQL.noop, + ), + ] diff --git a/apps/workflow/migrations/0018_text_unset_constraints.py b/apps/workflow/migrations/0018_text_unset_constraints.py new file mode 100644 index 000000000..e29c9fc31 --- /dev/null +++ b/apps/workflow/migrations/0018_text_unset_constraints.py @@ -0,0 +1,47 @@ +"""Add the not-blank CHECK constraints for workflow's newly nullable columns. + +Separate from 0017_text_unset_is_null because Postgres refuses to ALTER a table that +still has pending trigger events from an UPDATE in the same transaction — +the data pass and the constraint pass must be different migrations. +""" + +from django.db import migrations + +COLUMNS_BY_TABLE = { + "workflow_aiprovider": [ + "model_name" + ], + "workflow_searchtelemetryevent": [ + "normalized_query", + "query", + "selected_label", + "selected_result_id", + "source" + ], + "workflow_sessionreplayrecording": [ + "user_agent" + ], + "workflow_xeroapp": [ + "webhook_key" + ] +} + + +class Migration(migrations.Migration): + dependencies = [ + ("workflow", "0017_text_unset_is_null"), + ] + + operations = [ + migrations.RunSQL( + sql=( + f'ALTER TABLE {table} ADD CONSTRAINT {col}_not_blank ' + f'CHECK ("{col}" <> \'\')' + ), + reverse_sql=( + f"ALTER TABLE {table} DROP CONSTRAINT IF EXISTS {col}_not_blank" + ), + ) + for table, columns in COLUMNS_BY_TABLE.items() + for col in columns + ] diff --git a/apps/workflow/migrations/0019_company_defaults_gst_rate.py b/apps/workflow/migrations/0019_company_defaults_gst_rate.py new file mode 100644 index 000000000..f9cec3ce4 --- /dev/null +++ b/apps/workflow/migrations/0019_company_defaults_gst_rate.py @@ -0,0 +1,25 @@ +# Generated by Django 6.0.7 on 2026-07-31 21:21 + +from decimal import Decimal + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("workflow", "0018_text_unset_constraints"), + ] + + operations = [ + migrations.AddField( + model_name="companydefaults", + name="gst_rate", + field=models.DecimalField( + decimal_places=4, + default=Decimal("0.1500"), + help_text="Sales tax rate applied to amounts DocketWorks quotes before Xero has issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero remains authoritative for tax on invoices that exist.", + max_digits=5, + ), + ), + ] diff --git a/apps/workflow/migrations/0020_company_defaults_gst_rate_bounds.py b/apps/workflow/migrations/0020_company_defaults_gst_rate_bounds.py new file mode 100644 index 000000000..42447e083 --- /dev/null +++ b/apps/workflow/migrations/0020_company_defaults_gst_rate_bounds.py @@ -0,0 +1,38 @@ +# Generated by Django 6.0.7 on 2026-08-01 07:24 + +from decimal import Decimal + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("company", "0012_text_unset_constraints"), + ("workflow", "0019_company_defaults_gst_rate"), + ] + + operations = [ + migrations.AlterField( + model_name="companydefaults", + name="gst_rate", + field=models.DecimalField( + decimal_places=4, + default=Decimal("0.1500"), + help_text="Sales tax rate applied to amounts DocketWorks quotes before Xero has issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero remains authoritative for tax on invoices that exist.", + max_digits=5, + validators=[ + django.core.validators.MinValueValidator(Decimal("0")), + django.core.validators.MaxValueValidator(Decimal("0.9999")), + ], + ), + ), + migrations.AddConstraint( + model_name="companydefaults", + constraint=models.CheckConstraint( + condition=models.Q(("gst_rate__gte", 0), ("gst_rate__lt", 1)), + name="companydefaults_gst_rate_is_a_fraction", + ), + ), + ] diff --git a/apps/workflow/models/ai_provider.py b/apps/workflow/models/ai_provider.py index 82a7291bb..0e809f19f 100644 --- a/apps/workflow/models/ai_provider.py +++ b/apps/workflow/models/ai_provider.py @@ -15,6 +15,7 @@ class AIProvider(models.Model): max_length=100, help_text="Model name (e.g., gemini-flash-latest)", blank=True, + null=True, ) provider_type = models.CharField( max_length=20, choices=AIProviderTypes, help_text="Type of AI provider" diff --git a/apps/workflow/models/company_defaults.py b/apps/workflow/models/company_defaults.py index 9fc58ce54..9dfbc9ed0 100644 --- a/apps/workflow/models/company_defaults.py +++ b/apps/workflow/models/company_defaults.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from decimal import Decimal +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models.base import ModelBase from solo.models import SingletonModel @@ -20,6 +21,20 @@ class CompanyDefaults(SingletonModel): ) time_markup = models.DecimalField(max_digits=5, decimal_places=2, default=0.3) materials_markup = models.DecimalField(max_digits=5, decimal_places=2, default=0.2) + gst_rate = models.DecimalField( + max_digits=5, + decimal_places=4, + default=Decimal("0.1500"), + validators=[ + MinValueValidator(Decimal("0")), + MaxValueValidator(Decimal("0.9999")), + ], + help_text=( + "Sales tax rate applied to amounts DocketWorks quotes before Xero has " + "issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero remains " + "authoritative for tax on invoices that exist." + ), + ) wage_rate = models.DecimalField( max_digits=6, decimal_places=2, default=32.00 ) # rate per hour @@ -358,6 +373,13 @@ class Meta: condition=models.Q(id=1), name="companydefaults_singleton", ), + # The validators above only fire through a serializer; the admin, + # management commands and the Xero sync all write straight to the + # model, so the rate is held to a fraction at the database. + models.CheckConstraint( + condition=models.Q(gst_rate__gte=0) & models.Q(gst_rate__lt=1), + name="companydefaults_gst_rate_is_a_fraction", + ), ] @classmethod diff --git a/apps/workflow/models/search_telemetry_event.py b/apps/workflow/models/search_telemetry_event.py index 63b7274e4..0de2a392c 100644 --- a/apps/workflow/models/search_telemetry_event.py +++ b/apps/workflow/models/search_telemetry_event.py @@ -20,15 +20,15 @@ class Domain(models.TextChoices): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) event_type = models.CharField(max_length=20, choices=EventType.choices) domain = models.CharField(max_length=20, choices=Domain.choices) - source = models.CharField(max_length=100, blank=True) - query = models.CharField(max_length=255, blank=True) - normalized_query = models.CharField(max_length=255, blank=True) + source = models.CharField(max_length=100, blank=True, null=True) + query = models.CharField(max_length=255, blank=True, null=True) + normalized_query = models.CharField(max_length=255, blank=True, null=True) filters = models.JSONField(default=dict, blank=True) result_count = models.PositiveIntegerField(default=0) returned_count = models.PositiveIntegerField(default=0) returned_result_ids = models.JSONField(default=list, blank=True) - selected_result_id = models.CharField(max_length=255, blank=True) - selected_label = models.CharField(max_length=255, blank=True) + selected_result_id = models.CharField(max_length=255, blank=True, null=True) + selected_label = models.CharField(max_length=255, blank=True, null=True) selected_rank = models.PositiveIntegerField(null=True, blank=True) metadata = models.JSONField(default=dict, blank=True) source_event_hash = models.CharField( diff --git a/apps/workflow/models/session_replay.py b/apps/workflow/models/session_replay.py index 6e03c34a3..083b14ca1 100644 --- a/apps/workflow/models/session_replay.py +++ b/apps/workflow/models/session_replay.py @@ -19,7 +19,7 @@ class SessionReplayRecording(models.Model): initial_path = models.CharField(max_length=500) latest_path = models.CharField(max_length=500) job_id = models.UUIDField(blank=True, null=True, db_index=True) - user_agent = models.TextField(blank=True) + user_agent = models.TextField(blank=True, null=True) viewport_width = models.PositiveIntegerField(blank=True, null=True) viewport_height = models.PositiveIntegerField(blank=True, null=True) event_count = models.PositiveIntegerField(default=0) diff --git a/apps/workflow/models/settings_metadata.py b/apps/workflow/models/settings_metadata.py index 15b85b593..79e49bf4e 100644 --- a/apps/workflow/models/settings_metadata.py +++ b/apps/workflow/models/settings_metadata.py @@ -111,6 +111,7 @@ def get_section_info(cls, key: str) -> tuple[str, str, int] | None: # Finances "time_markup": "finances", "materials_markup": "finances", + "gst_rate": "finances", "wage_rate": "finances", "annual_leave_loading": "finances", "financial_year_start_month": "finances", diff --git a/apps/workflow/models/xero_app.py b/apps/workflow/models/xero_app.py index 380b6b3b7..2f833b2ba 100644 --- a/apps/workflow/models/xero_app.py +++ b/apps/workflow/models/xero_app.py @@ -24,10 +24,10 @@ class XeroApp(models.Model): client_secret = models.CharField(max_length=256) redirect_uri = models.CharField(max_length=512) # Xero webhook signing key — set per-app in Xero's developer portal. - # Empty string means the app cannot verify webhooks; the verifier loops - # over every non-empty webhook_key and accepts if any HMAC matches, so - # both apps in a rotation pair must each have their own key set. - webhook_key = models.CharField(max_length=128, blank=True, default="") + # NULL means the app cannot verify webhooks; the verifier loops over every + # non-NULL webhook_key and accepts if any HMAC matches, so both apps in a + # rotation pair must each have their own key set. + webhook_key = models.CharField(max_length=128, blank=True, null=True) is_active = models.BooleanField(default=False) diff --git a/apps/workflow/serializers.py b/apps/workflow/serializers.py index dee7a5053..22e353c88 100644 --- a/apps/workflow/serializers.py +++ b/apps/workflow/serializers.py @@ -1,8 +1,9 @@ -from typing import Any +from collections.abc import Mapping from rest_framework import serializers from apps.workflow.accounting.types import DocumentTheme +from apps.workflow.serializers_base import NullUnsetModelSerializer # Existing models used in this serializer module from .models import ( @@ -36,7 +37,7 @@ def _build_logo_url(instance: CompanyDefaults, field_name: str) -> str | None: return field_file.url -class NotebookLmLinkSerializer(serializers.ModelSerializer[NotebookLmLink]): +class NotebookLmLinkSerializer(NullUnsetModelSerializer[NotebookLmLink]): """Serializer for NotebookLM training-menu links (read + write).""" class Meta: @@ -51,7 +52,7 @@ class Meta: ) -class AIProviderSerializer(serializers.ModelSerializer): +class AIProviderSerializer(NullUnsetModelSerializer[AIProvider]): """ Serializer for reading AIProvider instances. This serializer is read-only and excludes the `api_key` for security. @@ -68,7 +69,7 @@ class Meta: ) -class CompanyDefaultsSerializer(serializers.ModelSerializer): +class CompanyDefaultsSerializer(NullUnsetModelSerializer[CompanyDefaults]): logo = serializers.ImageField(required=False, allow_null=True, write_only=True) logo_wide = serializers.ImageField(required=False, allow_null=True, write_only=True) logo_url = serializers.SerializerMethodField(read_only=True) @@ -78,11 +79,6 @@ class CompanyDefaultsSerializer(serializers.ModelSerializer): max_length=4000, trim_whitespace=False, ) - optional_url_fields = ( - "master_quote_template_url", - "gdrive_quotes_folder_url", - "company_url", - ) def get_logo_url(self, obj: CompanyDefaults) -> str | None: return _build_logo_url(obj, "logo") @@ -90,14 +86,6 @@ def get_logo_url(self, obj: CompanyDefaults) -> str | None: def get_logo_wide_url(self, obj: CompanyDefaults) -> str | None: return _build_logo_url(obj, "logo_wide") - def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: - for field_name in self.optional_url_fields: - if attrs.get(field_name) == "": - attrs[field_name] = None - else: - pass - return attrs - def validate_xero_quote_terms(self, value: str) -> str: """Reject whitespace-only terms. @@ -114,19 +102,19 @@ class Meta: read_only_fields = tuple(COMPANY_DEFAULTS_READ_ONLY_FIELDS) -class XeroAccountSerializer(serializers.ModelSerializer): +class XeroAccountSerializer(NullUnsetModelSerializer[XeroAccount]): class Meta: model = XeroAccount fields = "__all__" -class XeroPayItemSerializer(serializers.ModelSerializer): +class XeroPayItemSerializer(NullUnsetModelSerializer[XeroPayItem]): class Meta: model = XeroPayItem fields = "__all__" -class XeroAppSerializer(serializers.ModelSerializer): +class XeroAppSerializer(NullUnsetModelSerializer[XeroApp]): """List / detail / PATCH serializer for XeroApp. client_secret and webhook_key are write-only — never returned. The @@ -192,7 +180,7 @@ class XeroAppCreateSerializer(XeroAppSerializer): webhook_key = serializers.CharField(write_only=True, required=True) -class AIProviderCreateUpdateSerializer(serializers.ModelSerializer): +class AIProviderCreateUpdateSerializer(NullUnsetModelSerializer[AIProvider]): """ Serializer for creating and updating AIProvider instances. This serializer handles the `api_key` securely by making it write-only. @@ -203,9 +191,8 @@ class AIProviderCreateUpdateSerializer(serializers.ModelSerializer): api_key = serializers.CharField( write_only=True, required=False, # Not required on update, but validated below for create. - allow_blank=True, style={"input_type": "password"}, - help_text="API Key for the provider. Leave blank to keep unchanged on update.", + help_text="API Key for the provider. Omit to keep unchanged on update.", ) class Meta: @@ -235,7 +222,7 @@ def validate(self, data): # --------------------------------------------------------------------------- -class XeroErrorSerializer(serializers.ModelSerializer): +class XeroErrorSerializer(NullUnsetModelSerializer[XeroError]): """ Basic serializer to expose all fields of XeroError. @@ -285,7 +272,7 @@ class XeroOperationResponseSerializer(serializers.Serializer): success = serializers.BooleanField() error = serializers.CharField(required=False) messages = serializers.ListField(child=serializers.CharField(), required=False) - online_url = serializers.URLField(required=False, allow_blank=True) + online_url = serializers.URLField(required=False, allow_null=True) xero_id = serializers.UUIDField(required=True) @@ -355,7 +342,7 @@ class XeroDocumentSuccessResponseSerializer(serializers.Serializer): ) online_url = serializers.URLField( help_text="Direct link to the document in Xero.", - allow_blank=True, + allow_null=True, required=False, ) messages = serializers.ListField( @@ -472,7 +459,7 @@ class XeroPingResponseSerializer(serializers.Serializer): # --------------------------------------------------------------------------- -class AppErrorSerializer(serializers.ModelSerializer): +class AppErrorSerializer(NullUnsetModelSerializer[AppError]): """Basic serializer for AppError instances.""" class Meta: @@ -519,7 +506,9 @@ class SessionReplayRecordingCreateSerializer(serializers.Serializer): job_id = serializers.UUIDField(required=False, allow_null=True) -class SessionReplayRecordingSerializer(serializers.ModelSerializer): +class SessionReplayRecordingSerializer( + NullUnsetModelSerializer[SessionReplayRecording] +): user_email = serializers.EmailField(source="user.email", read_only=True) class Meta: @@ -565,7 +554,7 @@ class SessionReplayChunkCreateSerializer(serializers.Serializer): ) -class SessionReplayChunkSerializer(serializers.ModelSerializer): +class SessionReplayChunkSerializer(NullUnsetModelSerializer[SessionReplayChunk]): class Meta: model = SessionReplayChunk fields = ( @@ -688,3 +677,16 @@ class XeroBrandingThemeSerializer(serializers.Serializer[DocumentTheme]): branding_theme_id = serializers.UUIDField(source="external_id") name = serializers.CharField() is_default = serializers.BooleanField() + + +class AppErrorDetailsSerializer(serializers.Serializer[Mapping[str, object]]): + """Reference to the persisted failure backing an API error response.""" + + error_id = serializers.UUIDField() + + +class AppErrorResponseSerializer(serializers.Serializer[Mapping[str, object]]): + """Shared exception-derived API error contract.""" + + error = serializers.CharField() + details = AppErrorDetailsSerializer(required=False) diff --git a/apps/workflow/serializers_base.py b/apps/workflow/serializers_base.py new file mode 100644 index 000000000..dd25089f8 --- /dev/null +++ b/apps/workflow/serializers_base.py @@ -0,0 +1,35 @@ +"""Serializer base enforcing that NULL is the only unset. + +A nullable text column stores "" nowhere — a CHECK constraint forbids it (see +CLAUDE.md). DRF's ModelSerializer would otherwise infer ``allow_blank=True`` +from the model's ``blank=True`` and accept "" happily, which then fails in the +database as an IntegrityError 500 instead of a 400 at the boundary. + +Deriving the rule from the model field means it holds for every column without +a per-field declaration, and keeps holding when a column is added. +""" + +from __future__ import annotations + +from typing import Any, TypeVar + +from django.db import models +from rest_framework import serializers + +_ModelT = TypeVar("_ModelT", bound=models.Model) + + +class NullUnsetModelSerializer(serializers.ModelSerializer[_ModelT]): + """ModelSerializer that rejects "" wherever NULL is the column's unset.""" + + def build_standard_field( + self, field_name: str, model_field: models.Field[Any, Any] + ) -> tuple[type[serializers.Field[Any, Any, Any, Any]], dict[str, Any]]: + field_class, field_kwargs = super().build_standard_field( + field_name, model_field + ) + if model_field.null and field_kwargs.get("allow_blank"): + field_kwargs["allow_blank"] = False + else: + pass # Non-nullable columns keep "" as their own empty value. + return field_class, field_kwargs diff --git a/apps/workflow/services/__init__.py b/apps/workflow/services/__init__.py index 653b969c4..cae4c57b9 100644 --- a/apps/workflow/services/__init__.py +++ b/apps/workflow/services/__init__.py @@ -34,6 +34,7 @@ persist_app_error, persist_xero_error, ) + from .http_error_service import http_status_for_exception from .instance_onboarding import finalize_instance_onboarding from .llm_service import LLMService, quick_completion, quick_json_completion from .request import get_client_ip @@ -75,6 +76,7 @@ "finalize_instance_onboarding", "get_client_ip", "get_closed_e2e_windows", + "http_status_for_exception", "is_test_company_name", "list_app_errors", "list_grouped_app_errors", diff --git a/apps/workflow/services/dev_demo_export_scrubber.py b/apps/workflow/services/dev_demo_export_scrubber.py index 86a49085a..34178abb1 100644 --- a/apps/workflow/services/dev_demo_export_scrubber.py +++ b/apps/workflow/services/dev_demo_export_scrubber.py @@ -44,7 +44,13 @@ class ScrubResult: rows: int -def _stable_label(value: str, prefix: str) -> str: +def _stable_label(value: str | None, prefix: str) -> str: + """Stable pseudonym for ``value``; "" when there is nothing to redact. + + Returns str because non-nullable columns (ServiceAPIKey.key, + PhoneCallRecord.provider_call_id) also use it. Nullable columns convert + the empty result to NULL at the call site. + """ if not value: return "" digest = hashlib.sha256(value.encode("utf-8")).hexdigest()[:12] @@ -69,7 +75,7 @@ def _truncate_existing_tables(using: str, tables: tuple[str, ...]) -> list[Scrub def _redact_xero_apps(using: str) -> ScrubResult: rows = XeroApp.objects.using(using).update( client_secret="", - webhook_key="", + webhook_key=None, token_type=None, access_token=None, refresh_token=None, @@ -105,7 +111,7 @@ def _redact_phone_provider_settings(using: str) -> ScrubResult: base_url=None, username="", password="", - account_code="", + account_code=None, ) return ScrubResult("crm_phoneprovidersettings", rows) @@ -119,7 +125,7 @@ def _redact_phone_endpoints(using: str) -> ScrubResult: endpoint.normalized_number, "demo-endpoint", ), - provider_account_code="", + provider_account_code=None, provider_metadata={}, ) rows += 1 @@ -131,11 +137,13 @@ def _redact_phone_calls(using: str) -> ScrubResult: for call in PhoneCallRecord.objects.using(using).all().iterator(): call.provider_call_id = _stable_label(str(call.id), "demo-call") call.account_code = "demo-account" - call.description = "" - call.origin = _stable_label(call.origin, "demo-number") - call.destination = _stable_label(call.destination, "demo-number") - call.our_number = _stable_label(call.our_number, "demo-number") - call.external_number = _stable_label(call.external_number, "demo-number") + call.description = None + call.origin = _stable_label(call.origin, "demo-number") or None + call.destination = _stable_label(call.destination, "demo-number") or None + call.our_number = _stable_label(call.our_number, "demo-number") or None + call.external_number = ( + _stable_label(call.external_number, "demo-number") or None + ) call.raw_json = {} call.save( using=using, @@ -172,9 +180,10 @@ def _redact_session_replays(using: str) -> list[ScrubResult]: recording_rows = SessionReplayRecording.objects.using(using).update( initial_path="/redacted", latest_path="/redacted", - user_agent="", + user_agent=None, ) chunk_rows = SessionReplayChunk.objects.using(using).update( + # NOT NULL: redaction clears this in place rather than nulling it. storage_path="", sha256="", path="/redacted", @@ -187,12 +196,12 @@ def _redact_session_replays(using: str) -> list[ScrubResult]: def _redact_activity_payloads(using: str) -> list[ScrubResult]: search_rows = SearchTelemetryEvent.objects.using(using).update( - query="", - normalized_query="", + query=None, + normalized_query=None, filters={}, returned_result_ids=[], - selected_result_id="", - selected_label="", + selected_result_id=None, + selected_label=None, metadata={}, ) quote_chat_rows = JobQuoteChat.objects.using(using).update( @@ -243,7 +252,7 @@ def scrub_dev_demo_export(using: str = SCRUB_ALIAS) -> list[ScrubResult]: results.append( ScrubResult( "workflow_aiprovider", - AIProvider.objects.using(using).update(api_key=""), + AIProvider.objects.using(using).update(api_key=None), ) ) results.append(_redact_service_api_keys(using)) diff --git a/apps/workflow/services/http_error_service.py b/apps/workflow/services/http_error_service.py new file mode 100644 index 000000000..43e95649d --- /dev/null +++ b/apps/workflow/services/http_error_service.py @@ -0,0 +1,33 @@ +"""Map boundary exceptions to their HTTP status codes.""" + +from django.core.exceptions import ( + ObjectDoesNotExist, +) +from django.core.exceptions import PermissionDenied as DjangoPermissionDenied +from django.core.exceptions import ValidationError as DjangoValidationError +from django.db import IntegrityError +from django.http import Http404 +from rest_framework import status +from rest_framework.exceptions import NotFound +from rest_framework.exceptions import PermissionDenied as DRFPermissionDenied +from rest_framework.exceptions import ValidationError as DRFValidationError + +from apps.workflow.exceptions import PreconditionFailedError + + +def http_status_for_exception(error: Exception) -> int: + """Return the status that represents ``error`` at an HTTP boundary.""" + if isinstance(error, PreconditionFailedError): + return status.HTTP_412_PRECONDITION_FAILED + if isinstance(error, (Http404, NotFound, ObjectDoesNotExist)): + return status.HTTP_404_NOT_FOUND + if isinstance(error, IntegrityError): + return status.HTTP_409_CONFLICT + if isinstance( + error, + (PermissionError, DjangoPermissionDenied, DRFPermissionDenied), + ): + return status.HTTP_403_FORBIDDEN + if isinstance(error, (ValueError, DjangoValidationError, DRFValidationError)): + return status.HTTP_400_BAD_REQUEST + return status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/apps/workflow/services/session_replay_service.py b/apps/workflow/services/session_replay_service.py index c22f92d29..a98a5e9d1 100644 --- a/apps/workflow/services/session_replay_service.py +++ b/apps/workflow/services/session_replay_service.py @@ -77,7 +77,7 @@ def create_recording( *, user, initial_path: str, - user_agent: str, + user_agent: str | None, viewport_width: int | None = None, viewport_height: int | None = None, job_id: str | None = None, diff --git a/apps/workflow/tests/test_access_logging_middleware.py b/apps/workflow/tests/test_access_logging_middleware.py index c76d4261e..8cb48177e 100644 --- a/apps/workflow/tests/test_access_logging_middleware.py +++ b/apps/workflow/tests/test_access_logging_middleware.py @@ -9,7 +9,7 @@ @pytest.mark.django_db -def test_access_log_includes_response_status_and_duration(): +def test_access_log_includes_response_status_and_duration() -> None: staff = Staff.objects.create_user( email="access-log@example.test", password="testpass123", @@ -29,10 +29,7 @@ def test_access_log_includes_response_status_and_duration(): assert response.status_code == 418 log_info.assert_called_once() - log_format, *log_args = log_info.call_args.args - assert log_format == ( - "%s\tmethod=%s\tstatus=%s\tduration_ms=%.2f\treplay=%s\tuser=%s\tpath=%s" - ) + _, *log_args = log_info.call_args.args assert log_args[1] == "GET" assert log_args[2] == 418 assert log_args[3] == pytest.approx(123.45) @@ -41,7 +38,7 @@ def test_access_log_includes_response_status_and_duration(): assert log_args[6] == "/api/job/kanban/" -def test_access_log_skips_unauthenticated_requests(): +def test_access_log_skips_unauthenticated_requests() -> None: request = RequestFactory().get("/api/job/kanban/") request.user = type("AnonymousUser", (), {"is_authenticated": False})() middleware = AccessLoggingMiddleware(lambda _request: HttpResponse(status=401)) diff --git a/apps/workflow/tests/test_api_schema_coverage.py b/apps/workflow/tests/test_api_schema_coverage.py index 9b82dc953..86e705ec0 100644 --- a/apps/workflow/tests/test_api_schema_coverage.py +++ b/apps/workflow/tests/test_api_schema_coverage.py @@ -112,7 +112,7 @@ def _get_schema_paths(self) -> set[str]: ) return {path.lstrip("/").rstrip("/") for path in paths} - def test_all_api_endpoints_in_schema(self): + def test_all_api_endpoints_in_schema(self) -> None: """Every API endpoint must be documented in the OpenAPI schema.""" all_patterns = self._get_all_url_patterns() schema_paths = self._get_schema_paths() diff --git a/apps/workflow/tests/test_backup_scripts.py b/apps/workflow/tests/test_backup_scripts.py index 72f885eaf..d0d31b991 100644 --- a/apps/workflow/tests/test_backup_scripts.py +++ b/apps/workflow/tests/test_backup_scripts.py @@ -16,7 +16,6 @@ REPO_ROOT = Path(__file__).resolve().parents[3] CLEANUP_BACKUPS = REPO_ROOT / "scripts" / "cleanup_backups.py" -BACKUP_INSTANCE_FILES = REPO_ROOT / "scripts" / "backup_instance_files.sh" PULL_PROD_BACKUP = REPO_ROOT / "scripts" / "pull_prod_backup.sh" @@ -241,7 +240,6 @@ def purge_remote_entries( self.assertEqual(events[0][0], "copy") self.assertIn("daily_20260601.sql.gz", events[0][1]) self.assertEqual(events[1], ("purge", ["daily_20260601.sql.gz"])) - self.assertEqual(cleanup.REMOTE_BASE, "gdrive:dw_backups") self.assertFalse((backup_dir / "daily_20260601.sql.gz").exists()) def test_cleanup_copy_failure_prevents_local_retention_delete(self) -> None: @@ -324,22 +322,6 @@ def test_cleanup_prunes_daily_sha_with_expired_dump(self) -> None: self.assertFalse((backup_dir / "daily_20260601.sha").exists()) self.assertTrue((backup_dir / "daily_20260615.sha").exists()) - def test_file_backup_script_is_incremental_and_scoped(self) -> None: - content = BACKUP_INSTANCE_FILES.read_text() - - self.assertIn('backup_dir "phone-recordings"', content) - self.assertIn('backup_dir "session-replays"', content) - self.assertIn('backup_dir "mediafiles"', content) - self.assertNotIn('backup_dir "dropbox"', content) - self.assertNotIn('backup_dir "adhoc"', content) - self.assertNotIn("tar ", content) - self.assertIn("rclone sync", content) - self.assertIn("--backup-dir", content) - self.assertIn('REMOTE_BASE="gdrive:dw_backups/files"', content) - self.assertIn("ARCHIVE_RETENTION_DAYS=30", content) - self.assertIn("rclone purge", content) - self.assertIn("refusing to back up symlinked directory", content) - class BackportCommandErrorPersistenceTests(TestCase): def test_prelogged_scrub_failure_is_not_persisted_again(self) -> None: diff --git a/apps/workflow/tests/test_company_defaults_api.py b/apps/workflow/tests/test_company_defaults_api.py index c3ad6c02c..2dbb02374 100644 --- a/apps/workflow/tests/test_company_defaults_api.py +++ b/apps/workflow/tests/test_company_defaults_api.py @@ -1,27 +1,28 @@ import uuid +from decimal import Decimal -from django.db import connection +from django.db import IntegrityError, connection, transaction from django.test.utils import CaptureQueriesContext from rest_framework import status from rest_framework.test import APIClient -from apps.testing import BaseTestCase +from apps.testing import BaseAPITestCase from apps.workflow.models import CompanyDefaults -class CompanyDefaultsAPITests(BaseTestCase): - def setUp(self): - self.client = APIClient() +class CompanyDefaultsAPITests(BaseAPITestCase): + def setUp(self) -> None: + super().setUp() self.client.force_authenticate(user=self.test_staff) - def test_get_returns_shop_company_fk_without_name_alias(self): + def test_get_returns_shop_company_fk_without_name_alias(self) -> None: response = self.client.get("/api/company-defaults/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("shop_company", response.data) self.assertNotIn("shop_company_name", response.data) - def test_get_does_not_query_company_for_shop_company_display_name(self): + def test_get_does_not_query_company_for_shop_company_display_name(self) -> None: with CaptureQueriesContext(connection) as captured: response = self.client.get("/api/company-defaults/") @@ -33,13 +34,14 @@ def test_get_does_not_query_company_for_shop_company_display_name(self): ] self.assertEqual(company_queries, []) - def test_patch_canonicalizes_blank_optional_urls_to_null(self): + def test_patch_clears_optional_urls_with_null(self) -> None: + """Clearing a URL sends null; NULL is the only unset these columns hold.""" response = self.client.patch( "/api/company-defaults/", { - "master_quote_template_url": "", - "gdrive_quotes_folder_url": "", - "company_url": "", + "master_quote_template_url": None, + "gdrive_quotes_folder_url": None, + "company_url": None, }, format="json", ) @@ -54,6 +56,17 @@ def test_patch_canonicalizes_blank_optional_urls_to_null(self): self.assertIsNone(company_defaults.gdrive_quotes_folder_url) self.assertIsNone(company_defaults.company_url) + def test_patch_rejects_blank_optional_urls(self) -> None: + """ "" is not a second way to say unset — it is rejected, not coerced.""" + response = self.client.patch( + "/api/company-defaults/", + {"company_url": ""}, + format="json", + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("company_url", response.json()) + def test_patch_persists_and_clears_xero_sales_branding_theme(self) -> None: """Admins can operate the required Xero document theme setting.""" theme_id = uuid.uuid4() @@ -155,3 +168,24 @@ def test_patch_rejects_xero_quote_terms_over_4000_characters(self) -> None: payload["xero_quote_terms"], ["Ensure this field has no more than 4000 characters."], ) + + def test_patch_rejects_a_gst_rate_outside_a_fraction(self) -> None: + """The rate is a fraction: 0.15 means 15%, so 15 would tax 1500%.""" + for rate in (Decimal("-0.1500"), Decimal("15")): + with self.subTest(rate=rate): + response = self.client.patch( + "/api/company-defaults/", {"gst_rate": rate}, format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("gst_rate", response.json()) + + def test_a_gst_rate_outside_a_fraction_cannot_reach_the_database(self) -> None: + """The admin, commands and Xero sync all bypass the serializer.""" + defaults = CompanyDefaults.get_solo() + + for rate in (Decimal("-0.1500"), Decimal("1.0000")): + with self.subTest(rate=rate): + defaults.gst_rate = rate + with self.assertRaises(IntegrityError), transaction.atomic(): + defaults.save() diff --git a/apps/workflow/tests/test_company_defaults_schema.py b/apps/workflow/tests/test_company_defaults_schema.py index a85f4c5c9..388d7303b 100644 --- a/apps/workflow/tests/test_company_defaults_schema.py +++ b/apps/workflow/tests/test_company_defaults_schema.py @@ -5,10 +5,9 @@ from django.db import models from django.test import TestCase from rest_framework import status -from rest_framework.test import APIClient from apps.accounts.models import Staff -from apps.testing import BaseTestCase +from apps.testing import BaseAPITestCase from apps.workflow.models import CompanyDefaults from apps.workflow.models.settings_metadata import ( COMPANY_DEFAULTS_FIELD_SECTIONS, @@ -21,7 +20,7 @@ class SettingsMetadataTests(TestCase): """Test settings metadata utilities.""" - def test_all_fields_have_sections(self): + def test_all_fields_have_sections(self) -> None: """Every CompanyDefaults field must have a section assigned.""" model = CompanyDefaults missing = [] @@ -39,7 +38,7 @@ def test_all_fields_have_sections(self): f"Add them to COMPANY_DEFAULTS_FIELD_SECTIONS.", ) - def test_all_sections_are_valid(self): + def test_all_sections_are_valid(self) -> None: """All section keys in mapping must be valid SettingsSection keys.""" valid_keys = {s[0] for s in SettingsSection.all_sections()} @@ -50,32 +49,32 @@ def test_all_sections_are_valid(self): f"Field '{field_name}' has invalid section '{section_key}'", ) - def test_ui_type_mapping_char_field(self): + def test_ui_type_mapping_char_field(self) -> None: """Test CharField maps to 'text' UI type.""" char_field = models.CharField(max_length=100) self.assertEqual(get_ui_type_for_field(char_field), "text") - def test_ui_type_mapping_boolean_field(self): + def test_ui_type_mapping_boolean_field(self) -> None: """Test BooleanField maps to 'boolean' UI type.""" bool_field = models.BooleanField() self.assertEqual(get_ui_type_for_field(bool_field), "boolean") - def test_ui_type_mapping_decimal_field(self): + def test_ui_type_mapping_decimal_field(self) -> None: """Test DecimalField maps to 'number' UI type.""" decimal_field = models.DecimalField(max_digits=5, decimal_places=2) self.assertEqual(get_ui_type_for_field(decimal_field), "number") - def test_ui_type_mapping_time_field(self): + def test_ui_type_mapping_time_field(self) -> None: """Test TimeField maps to 'time' UI type.""" time_field = models.TimeField() self.assertEqual(get_ui_type_for_field(time_field), "time") - def test_ui_type_mapping_url_field(self): + def test_ui_type_mapping_url_field(self) -> None: """Test URLField maps to 'url' UI type.""" url_field = models.URLField() self.assertEqual(get_ui_type_for_field(url_field), "url") - def test_get_field_metadata_structure(self): + def test_get_field_metadata_structure(self) -> None: """Test that get_field_metadata returns expected structure.""" char_field = models.CharField( max_length=100, @@ -94,7 +93,7 @@ def test_get_field_metadata_structure(self): self.assertEqual(metadata["help_text"], "Test help text") self.assertIn("section", metadata) - def test_settings_section_all_sections(self): + def test_settings_section_all_sections(self) -> None: """Test SettingsSection.all_sections() returns expected sections.""" sections = SettingsSection.all_sections() @@ -108,10 +107,10 @@ def test_settings_section_all_sections(self): self.assertIsInstance(section[1], str) # title self.assertIsInstance(section[2], int) # order - def test_settings_section_get_section_info(self): + def test_settings_section_get_section_info(self) -> None: """Test SettingsSection.get_section_info() returns correct info.""" info = SettingsSection.get_section_info("company") - self.assertIsNotNone(info) + assert info is not None self.assertEqual(info[0], "company") self.assertEqual(info[1], "Company") @@ -120,11 +119,11 @@ def test_settings_section_get_section_info(self): self.assertIsNone(info) -class CompanyDefaultsSchemaAPITests(BaseTestCase): +class CompanyDefaultsSchemaAPITests(BaseAPITestCase): """Test the schema API endpoint.""" - def setUp(self): - self.client = APIClient() + def setUp(self) -> None: + super().setUp() self.staff = Staff.objects.create_user( email="test@example.com", password="testpassword123", @@ -132,7 +131,7 @@ def setUp(self): last_name="User", ) - def test_schema_endpoint_returns_sections(self): + def test_schema_endpoint_returns_sections(self) -> None: """GET /api/company-defaults/schema/ returns section structure.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -141,7 +140,7 @@ def test_schema_endpoint_returns_sections(self): self.assertIn("sections", response.data) self.assertIsInstance(response.data["sections"], list) - def test_schema_sections_have_required_keys(self): + def test_schema_sections_have_required_keys(self) -> None: """Each section has key, title, order, and fields.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -152,7 +151,7 @@ def test_schema_sections_have_required_keys(self): self.assertIn("order", section) self.assertIn("fields", section) - def test_schema_fields_have_required_keys(self): + def test_schema_fields_have_required_keys(self) -> None: """Each field has key, label, type, required, section.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -165,12 +164,12 @@ def test_schema_fields_have_required_keys(self): self.assertIn("required", field) self.assertIn("section", field) - def test_internal_fields_excluded(self): + def test_internal_fields_excluded(self) -> None: """Internal fields like created_at are not in response.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") - all_field_keys = [] + all_field_keys: list[str] = [] for section in response.data["sections"]: all_field_keys.extend(f["key"] for f in section["fields"]) @@ -178,7 +177,7 @@ def test_internal_fields_excluded(self): self.assertNotIn("updated_at", all_field_keys) self.assertNotIn("is_primary", all_field_keys) - def test_sections_are_ordered(self): + def test_sections_are_ordered(self) -> None: """Sections are returned in order.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -186,13 +185,13 @@ def test_sections_are_ordered(self): orders = [s["order"] for s in response.data["sections"]] self.assertEqual(orders, sorted(orders)) - def test_requires_authentication(self): + def test_requires_authentication(self) -> None: """Unauthenticated requests are rejected.""" response = self.client.get("/api/company-defaults/schema/") self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def test_expected_sections_present(self): + def test_expected_sections_present(self) -> None: """Expected sections are present in response.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -210,7 +209,7 @@ def test_expected_sections_present(self): for expected in expected_sections: self.assertIn(expected, section_keys, f"Section '{expected}' missing") - def test_company_section_has_company_name(self): + def test_company_section_has_company_name(self) -> None: """Company section includes company_name field.""" self.client.force_authenticate(user=self.staff) response = self.client.get("/api/company-defaults/schema/") @@ -218,16 +217,15 @@ def test_company_section_has_company_name(self): company_section = next( (s for s in response.data["sections"] if s["key"] == "company"), None ) - self.assertIsNotNone(company_section) + assert company_section is not None field_keys = [f["key"] for f in company_section["fields"]] self.assertIn("company_name", field_keys) def test_xero_section_exposes_sales_branding_theme_selector(self) -> None: """The theme is operable through Company Settings, not hidden config.""" - client = APIClient() - client.force_authenticate(user=self.staff) - response = client.get("/api/company-defaults/schema/") + self.client.force_authenticate(user=self.staff) + response = self.client.get("/api/company-defaults/schema/") payload = response.json() xero_section = next( diff --git a/apps/workflow/tests/test_data_versions_view.py b/apps/workflow/tests/test_data_versions_view.py index dfd1da864..609dc4149 100644 --- a/apps/workflow/tests/test_data_versions_view.py +++ b/apps/workflow/tests/test_data_versions_view.py @@ -3,7 +3,9 @@ string so the frontend invalidates its cache and re-fetches. """ +from datetime import datetime from decimal import Decimal +from typing import TypedDict, Unpack, cast import pytest from django.utils import timezone @@ -39,12 +41,18 @@ def auth_client(office_staff): return api -def _stock(**overrides): - defaults = dict( - description="Test material", - quantity=Decimal("1.00"), - unit_cost=Decimal("10.00"), - ) +class _StockOverrides(TypedDict, total=False): + description: str + quantity: Decimal + unit_cost: Decimal + + +def _stock(**overrides: Unpack[_StockOverrides]) -> Stock: + defaults: _StockOverrides = { + "description": "Test material", + "quantity": Decimal("1.00"), + "unit_cost": Decimal("10.00"), + } defaults.update(overrides) return Stock.objects.create(**defaults) @@ -63,21 +71,38 @@ def kanban_prerequisites(db): ) -def _client(**overrides): - defaults = dict(name="Kanban Company", xero_last_modified=timezone.now()) +class _ClientOverrides(TypedDict, total=False): + name: str + xero_last_modified: datetime + + +def _client(**overrides: Unpack[_ClientOverrides]) -> Company: + defaults: _ClientOverrides = { + "name": "Kanban Company", + "xero_last_modified": timezone.now(), + } defaults.update(overrides) return Company.objects.create(**defaults) -def _job(staff, **overrides): - defaults = dict( - staff=staff, - company=_client(), - name="Kanban Job", - pricing_methodology="time_materials", - ) +class _JobOverrides(TypedDict, total=False): + company: Company + person: Person + name: str + pricing_methodology: str + + +def _job(staff: Staff, **overrides: Unpack[_JobOverrides]) -> Job: + defaults: _JobOverrides = { + "company": _client(), + "name": "Kanban Job", + "pricing_methodology": "time_materials", + } defaults.update(overrides) - return Job.objects.create(**defaults) + # Job.objects.create() requires a staff= kwarg that is not a model field, so + # django-stubs cannot type it (Any result, "staff" rejected as an attribute). + # The custom manager always returns a Job; the cast states that contract. + return cast(Job, Job.objects.create(staff=staff, **defaults)) def _phone_call() -> PhoneCallRecord: @@ -101,12 +126,15 @@ def test_get_returns_dict_with_dataset_keys(auth_client, db): body = resp.json() assert "stock" in body assert "kanban" in body + assert "kanban_related" in body assert "crm_calls" in body assert isinstance(body["stock"], str) assert isinstance(body["kanban"], str) + assert isinstance(body["kanban_related"], str) assert isinstance(body["crm_calls"], str) assert body["stock"] assert body["kanban"] + assert body["kanban_related"] assert body["crm_calls"] @@ -198,7 +226,7 @@ def test_assigning_staff_changes_kanban_version( assert before != after -def test_related_display_changes_change_kanban_version( +def test_related_display_changes_only_change_kanban_related_version( auth_client, office_staff, kanban_prerequisites ): company = _client(name="Original Company") @@ -208,23 +236,25 @@ def test_related_display_changes_change_kanban_version( person=person, ) _job(office_staff, company=company, person=person) - before = auth_client.get("/api/data-versions/").json()["kanban"] + before = auth_client.get("/api/data-versions/").json() person.name = "Updated Person" person.save(update_fields=["name"]) - after = auth_client.get("/api/data-versions/").json()["kanban"] - assert before != after + after = auth_client.get("/api/data-versions/").json() + assert before["kanban"] == after["kanban"] + assert before["kanban_related"] != after["kanban_related"] -def test_company_partial_save_changes_kanban_version( +def test_company_partial_save_only_changes_kanban_related_version( auth_client: APIClient, office_staff: Staff, kanban_prerequisites: None ) -> None: company = _client(name="Original Company") _job(office_staff, company=company) - before = auth_client.get("/api/data-versions/").json()["kanban"] + before = auth_client.get("/api/data-versions/").json() company.name = "Updated Company" company.save(update_fields=["name"]) - after = auth_client.get("/api/data-versions/").json()["kanban"] - assert before != after + after = auth_client.get("/api/data-versions/").json() + assert before["kanban"] == after["kanban"] + assert before["kanban_related"] != after["kanban_related"] def test_creating_phone_call_changes_crm_calls_version( diff --git a/apps/workflow/tests/test_dev_demo_export.py b/apps/workflow/tests/test_dev_demo_export.py index d4fc1013c..f94bfbaed 100644 --- a/apps/workflow/tests/test_dev_demo_export.py +++ b/apps/workflow/tests/test_dev_demo_export.py @@ -35,7 +35,7 @@ @pytest.mark.django_db -def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk(): +def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk() -> None: staff = Staff.objects.create_user( email="demo.staff@example.test", password="password", @@ -189,7 +189,7 @@ def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk(): assert xero_app.client_id == "client-id" assert xero_app.client_secret == "" assert xero_app.access_token is None - assert AIProvider.objects.get().api_key == "" + assert AIProvider.objects.get().api_key is None service_key.refresh_from_db() assert service_key.key.startswith("redacted-key-") @@ -200,14 +200,14 @@ def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk(): assert phone_settings.password == "" phone_endpoint = PhoneEndpoint.objects.get() assert phone_endpoint.number.startswith("demo-endpoint-") - assert phone_endpoint.provider_account_code == "" + assert phone_endpoint.provider_account_code is None call.refresh_from_db() assert call.duration_seconds == 180 assert call.charge == Decimal("1.2300") assert call.company == company - assert call.origin.startswith("demo-number-") - assert call.destination.startswith("demo-number-") + assert (call.origin or "").startswith("demo-number-") + assert (call.destination or "").startswith("demo-number-") assert call.raw_json == {} assert PhoneCallRecording.objects.count() == 0 @@ -225,7 +225,7 @@ def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk(): telemetry = SearchTelemetryEvent.objects.get() assert telemetry.domain == SearchTelemetryEvent.Domain.COMPANY - assert telemetry.query == "" + assert telemetry.query is None assert telemetry.metadata == {} pay_run.refresh_from_db() @@ -237,7 +237,7 @@ def test_dev_demo_scrub_preserves_business_signal_and_redacts_risk(): assert slip.raw_json == {} -def test_export_dev_demo_dump_refuses_non_dev_source_db(): +def test_export_dev_demo_dump_refuses_non_dev_source_db() -> None: with patch.dict(settings.DATABASES["default"], {"NAME": "dw_msm_prod"}): with pytest.raises(RuntimeError, match="must end in '_dev'"): call_command("export_dev_demo_dump") diff --git a/apps/workflow/tests/test_etag.py b/apps/workflow/tests/test_etag.py new file mode 100644 index 000000000..78b8ec6b6 --- /dev/null +++ b/apps/workflow/tests/test_etag.py @@ -0,0 +1,37 @@ +"""Shared ETag helpers must implement the application's HTTP contracts.""" + +from datetime import UTC, datetime +from uuid import uuid4 + +from django.test import SimpleTestCase + +from apps.workflow.etag import ( + generate_updated_at_etag, + if_match_satisfied, + if_none_match_satisfied, +) + + +class UpdatedAtETagTests(SimpleTestCase): + def setUp(self) -> None: + self.etag = generate_updated_at_etag( + "job", + uuid4(), + datetime(2026, 7, 30, 12, 0, 0, 123456, tzinfo=UTC), + ) + + def test_generates_a_strong_etag(self) -> None: + self.assertTrue(self.etag.startswith('"job:')) + self.assertFalse(self.etag.startswith("W/")) + + def test_if_match_accepts_the_exact_current_tag_in_a_header_list(self) -> None: + self.assertTrue(if_match_satisfied(f'"other", {self.etag}', self.etag)) + + def test_if_match_rejects_weak_wildcard_and_malformed_tags(self) -> None: + self.assertFalse(if_match_satisfied(f"W/{self.etag}", self.etag)) + self.assertFalse(if_match_satisfied("*", self.etag)) + self.assertFalse(if_match_satisfied("not-an-etag", self.etag)) + + def test_if_none_match_uses_weak_comparison_and_wildcard_semantics(self) -> None: + self.assertTrue(if_none_match_satisfied(f"W/{self.etag}", self.etag)) + self.assertTrue(if_none_match_satisfied("*", self.etag)) diff --git a/apps/workflow/tests/test_http_error_service.py b/apps/workflow/tests/test_http_error_service.py new file mode 100644 index 000000000..4df8ddc04 --- /dev/null +++ b/apps/workflow/tests/test_http_error_service.py @@ -0,0 +1,73 @@ +"""HTTP status mapping follows exception semantics across API boundaries.""" + +from django.core.exceptions import ( + ObjectDoesNotExist, +) +from django.core.exceptions import PermissionDenied as DjangoPermissionDenied +from django.core.exceptions import ValidationError as DjangoValidationError +from django.db import IntegrityError +from django.http import Http404 +from rest_framework import status +from rest_framework.exceptions import NotFound +from rest_framework.exceptions import PermissionDenied as DRFPermissionDenied +from rest_framework.exceptions import ValidationError as DRFValidationError +from rest_framework.test import APISimpleTestCase + +from apps.workflow.exceptions import PreconditionFailedError +from apps.workflow.services.http_error_service import http_status_for_exception + + +class HttpStatusForExceptionTests(APISimpleTestCase): + def test_maps_precondition_failure_to_412(self) -> None: + self.assertEqual( + http_status_for_exception(PreconditionFailedError("stale")), + status.HTTP_412_PRECONDITION_FAILED, + ) + + def test_maps_not_found_variants_to_404(self) -> None: + for error in ( + Http404("missing"), + NotFound("missing"), + ObjectDoesNotExist("missing"), + ): + with self.subTest(error=type(error).__name__): + self.assertEqual( + http_status_for_exception(error), + status.HTTP_404_NOT_FOUND, + ) + + def test_maps_integrity_failure_to_409(self) -> None: + self.assertEqual( + http_status_for_exception(IntegrityError("conflict")), + status.HTTP_409_CONFLICT, + ) + + def test_maps_permission_variants_to_403(self) -> None: + for error in ( + PermissionError("denied"), + DjangoPermissionDenied("denied"), + DRFPermissionDenied("denied"), + ): + with self.subTest(error=type(error).__name__): + self.assertEqual( + http_status_for_exception(error), + status.HTTP_403_FORBIDDEN, + ) + + def test_maps_validation_variants_to_400(self) -> None: + for error in ( + ValueError("invalid"), + DjangoValidationError("invalid"), + DRFValidationError("invalid"), + ): + with self.subTest(error=type(error).__name__): + self.assertEqual( + http_status_for_exception(error), + status.HTTP_400_BAD_REQUEST, + ) + + def test_maps_unknown_failure_to_500(self) -> None: + self.assertEqual( + http_status_for_exception(RuntimeError("unexpected")), + status.HTTP_500_INTERNAL_SERVER_ERROR, + ) diff --git a/apps/workflow/tests/test_localdate_regression.py b/apps/workflow/tests/test_localdate_regression.py index 5e1d43d64..787e4ebf2 100644 --- a/apps/workflow/tests/test_localdate_regression.py +++ b/apps/workflow/tests/test_localdate_regression.py @@ -10,14 +10,21 @@ import datetime from decimal import Decimal +from typing import TYPE_CHECKING, TypedDict, Unpack from unittest.mock import patch from django.test import TestCase from django.utils import timezone from freezegun import freeze_time +from apps.accounts.models import Staff +from apps.company.models import Company +from apps.job.models import Job from apps.testing import BaseTestCase +if TYPE_CHECKING: + from apps.workflow.views.xero.xero_invoice_manager import XeroInvoiceManager + # NZ ended DST on the first Sunday of April 2026 (April 5), so April 28 # is NZST = UTC+12. 23:30 UTC on April 27 == 11:30 NZST on April 28. FROZEN_UTC_MOMENT = "2026-04-27T23:30:00Z" @@ -26,15 +33,24 @@ DOCUMENT_THEME_ID = "00000000-0000-0000-0000-000000000286" -def _make_client(name="Localdate Test Company"): - from apps.company.models import Company - +def _make_client(name: str = "Localdate Test Company") -> Company: return Company.objects.create(name=name, xero_last_modified=timezone.now()) -def _make_job(company, staff, name="Localdate Test Job", **extra): - from apps.job.models import Job +class _JobExtras(TypedDict, total=False): + pricing_methodology: str + status: str + fully_invoiced: bool + paid: bool + rejected_flag: bool + +def _make_job( + company: Company, + staff: Staff, + name: str = "Localdate Test Job", + **extra: Unpack[_JobExtras], +) -> Job: job = Job(company=company, name=name, **extra) job.save(staff=staff) return job @@ -43,7 +59,7 @@ def _make_job(company, staff, name="Localdate Test Job", **extra): class FreezeTimeSanityTests(TestCase): """Belt-and-braces: confirm freeze_time + Pacific/Auckland disagree as expected.""" - def test_utc_and_nz_disagree_on_the_chosen_moment(self): + def test_utc_and_nz_disagree_on_the_chosen_moment(self) -> None: with freeze_time(FROZEN_UTC_MOMENT): # This assertion exists to prove the UTC/NZ-disagree premise that # every other test in this file relies on. The suppression that @@ -55,7 +71,7 @@ def test_utc_and_nz_disagree_on_the_chosen_moment(self): class WorkshopServiceLocalDateTests(TestCase): - def test_resolve_entry_date_returns_nz_date_when_no_param(self): + def test_resolve_entry_date_returns_nz_date_when_no_param(self) -> None: from apps.job.services.workshop_service import WorkshopTimesheetService with freeze_time(FROZEN_UTC_MOMENT): @@ -65,7 +81,7 @@ def test_resolve_entry_date_returns_nz_date_when_no_param(self): class PurchasingValidDateTests(TestCase): - def test_get_valid_date_returns_nz_date_when_value_falsy(self): + def test_get_valid_date_returns_nz_date_when_value_falsy(self) -> None: from apps.purchasing.services.purchasing_rest_service import ( PurchasingRestService, ) @@ -75,7 +91,7 @@ def test_get_valid_date_returns_nz_date_when_value_falsy(self): self.assertEqual(result, NZ_DATE) - def test_get_valid_date_returns_nz_date_when_value_invalid_string(self): + def test_get_valid_date_returns_nz_date_when_value_invalid_string(self) -> None: from apps.purchasing.services.purchasing_rest_service import ( PurchasingRestService, ) @@ -85,7 +101,7 @@ def test_get_valid_date_returns_nz_date_when_value_invalid_string(self): self.assertEqual(result, NZ_DATE) - def test_get_valid_date_returns_nz_date_when_value_wrong_type(self): + def test_get_valid_date_returns_nz_date_when_value_wrong_type(self) -> None: from apps.purchasing.services.purchasing_rest_service import ( PurchasingRestService, ) @@ -104,7 +120,7 @@ class StaffActiveOnDateTests(BaseTestCase): last day. """ - def test_is_currently_active_returns_false_on_their_last_day(self): + def test_is_currently_active_returns_false_on_their_last_day(self) -> None: from apps.accounts.models import Staff staff = Staff( @@ -117,7 +133,7 @@ def test_is_currently_active_returns_false_on_their_last_day(self): with freeze_time(FROZEN_UTC_MOMENT): self.assertFalse(staff.is_currently_active) - def test_currently_active_queryset_excludes_staff_who_left_today(self): + def test_currently_active_queryset_excludes_staff_who_left_today(self) -> None: from apps.accounts.models import Staff staff = Staff.objects.create_user( @@ -144,9 +160,7 @@ def test_currently_active_queryset_excludes_staff_who_left_today(self): class JobAgingLocalDateTests(BaseTestCase): """Aging calculations subtract dates; both halves must use the same tz.""" - def _make_aged_job(self): - from apps.job.models import Job - + def _make_aged_job(self) -> Job: company = _make_client("Aging Test Company") job = _make_job(company, self.test_staff, name="Aging Test Job") # Frozen "now" = UTC 2026-04-27 23:30 = NZ 2026-04-28 11:30 (UTC date @@ -171,7 +185,7 @@ def _make_aged_job(self): job.refresh_from_db() return job - def test_calculate_time_in_status_uses_localdate_both_sides(self): + def test_calculate_time_in_status_uses_localdate_both_sides(self) -> None: from apps.accounting.services.core import JobAgingService job = self._make_aged_job() @@ -181,7 +195,7 @@ def test_calculate_time_in_status_uses_localdate_both_sides(self): self.assertEqual(days, 3) - def test_get_timing_data_created_days_ago_uses_localdate_both_sides(self): + def test_get_timing_data_created_days_ago_uses_localdate_both_sides(self) -> None: """Covers core.py:955 (`created_at.date()`) and :959 (`now.date()`). `created_at` UTC midnight April 25 = NZ noon April 25 (both dates @@ -199,7 +213,7 @@ def test_get_timing_data_created_days_ago_uses_localdate_both_sides(self): self.assertEqual(timing["created_date"], "2026-04-25") self.assertEqual(timing["created_days_ago"], 3) - def test_get_last_activity_days_ago_uses_localdate(self): + def test_get_last_activity_days_ago_uses_localdate(self) -> None: """Covers core.py:1145 (`now.date() - activity_date_obj`). The job's most recent activity is its `updated_at`. We pin updated_at @@ -220,7 +234,7 @@ class XeroInvoiceLocalDateTests(BaseTestCase): """The invoice payload sent to Xero and the local Invoice record both must be stamped with the NZ calendar date.""" - def _make_manager(self, *, is_account_customer): + def _make_manager(self, *, is_account_customer: bool) -> "XeroInvoiceManager": from apps.workflow.views.xero.xero_invoice_manager import XeroInvoiceManager company = _make_client("Xero Invoice Test Company") @@ -234,7 +248,7 @@ def _make_manager(self, *, is_account_customer): ) return XeroInvoiceManager(company=company, job=job, staff=self.test_staff) - def test_build_payload_uses_nz_local_date(self): + def test_build_payload_uses_nz_local_date(self) -> None: manager = self._make_manager(is_account_customer=False) with ( @@ -247,7 +261,7 @@ def test_build_payload_uses_nz_local_date(self): self.assertEqual(payload.date, NZ_DATE) - def test_account_customer_due_date_is_20th_of_next_month(self): + def test_account_customer_due_date_is_20th_of_next_month(self) -> None: """`Company.is_account_customer=True` → due on the 20th of next month.""" manager = self._make_manager(is_account_customer=True) @@ -262,7 +276,7 @@ def test_account_customer_due_date_is_20th_of_next_month(self): # NZ "today" is 2026-04-28; 20th of next month = 2026-05-20. self.assertEqual(payload.due_date, datetime.date(2026, 5, 20)) - def test_cash_customer_due_date_is_same_day(self): + def test_cash_customer_due_date_is_same_day(self) -> None: """`Company.is_account_customer=False` → due same-day.""" manager = self._make_manager(is_account_customer=False) @@ -277,7 +291,7 @@ def test_cash_customer_due_date_is_same_day(self): self.assertEqual(payload.due_date, NZ_DATE) self.assertEqual(payload.date, payload.due_date) - def test_account_customer_due_date_at_31_day_month_boundary(self): + def test_account_customer_due_date_at_31_day_month_boundary(self) -> None: """The old `(today + 30d).replace(day=20)` formula returned *this* month's 20th when today was the 1st of a 31-day month (Jan, Mar, May, Jul, Aug, Oct, Dec). E.g. on May 1, +30d = May 31 → May 20, @@ -297,7 +311,7 @@ def test_account_customer_due_date_at_31_day_month_boundary(self): self.assertEqual(payload.date, datetime.date(2026, 5, 1)) self.assertEqual(payload.due_date, datetime.date(2026, 6, 20)) - def test_account_customer_due_date_rolls_over_year_in_december(self): + def test_account_customer_due_date_rolls_over_year_in_december(self) -> None: """December → January next year, day 20.""" manager = self._make_manager(is_account_customer=True) @@ -318,7 +332,7 @@ def test_account_customer_due_date_rolls_over_year_in_december(self): class XeroQuoteLocalDateTests(BaseTestCase): """Same as invoice, for quotes.""" - def test_build_payload_uses_nz_local_date(self): + def test_build_payload_uses_nz_local_date(self) -> None: from apps.workflow.views.xero.xero_quote_manager import XeroQuoteManager company = _make_client("Xero Quote Test Company") @@ -348,7 +362,7 @@ class StockServiceAccountingDateTests(BaseTestCase): A material consumed at NZ-morning currently lands on yesterday's books. """ - def test_consume_creates_cost_line_with_nz_local_accounting_date(self): + def test_consume_creates_cost_line_with_nz_local_accounting_date(self) -> None: from apps.purchasing.models import Stock from apps.purchasing.services.stock_service import consume_stock @@ -377,7 +391,7 @@ class DataQualityReportLocalDateTests(BaseTestCase): """The "archived_date" column in the data-quality report should report the NZ calendar date the job was last touched, not the UTC date.""" - def test_archived_date_uses_nz_local(self): + def test_archived_date_uses_nz_local(self) -> None: from apps.job.models import Job from apps.job.services.data_quality_report import ( ArchivedJobsComplianceService, diff --git a/apps/workflow/tests/test_null_is_the_only_unset.py b/apps/workflow/tests/test_null_is_the_only_unset.py new file mode 100644 index 000000000..914f7c7c0 --- /dev/null +++ b/apps/workflow/tests/test_null_is_the_only_unset.py @@ -0,0 +1,134 @@ +"""NULL is the only unset for nullable text columns — schema and wire. + +Two invariants that a new column or serializer can silently break. Both assert +observable state (the live constraint catalogue, bound serializer fields) rather +than source text, so they survive refactors and fail on the thing that matters. +""" + +import importlib +import inspect +import pkgutil +from typing import Any + +import pytest +from django.apps import apps +from django.db import connection, models +from rest_framework import serializers + +TEXT_FIELDS = ( + models.CharField, + models.TextField, + models.EmailField, + models.URLField, + models.SlugField, +) + +FIRST_PARTY_APPS = [ + "accounting", + "accounts", + "company", + "crm", + "job", + "operations", + "process", + "purchasing", + "quoting", + "timesheet", + "workflow", +] + + +def _nullable_text_columns() -> dict[type[models.Model], dict[str, str]]: + """Map each concrete first-party model to {field name: database column}.""" + result: dict[type[models.Model], dict[str, str]] = {} + for model in apps.get_models(): + if model._meta.app_label not in FIRST_PARTY_APPS: + continue + if model._meta.proxy or "historical" in model.__name__.lower(): + continue + columns: dict[str, str] = {} + for field in model._meta.local_fields: + if not isinstance(field, TEXT_FIELDS): + continue + if field.null and field.blank and field.column: + columns[field.name] = field.column + if columns: + result[model] = columns + return result + + +def _serializer_classes() -> list[type[serializers.BaseSerializer[Any]]]: + """Every serializer class defined under the first-party apps.""" + found: dict[str, type[serializers.BaseSerializer[Any]]] = {} + for app_label in FIRST_PARTY_APPS: + package = importlib.import_module(f"apps.{app_label}") + for _, name, _ in pkgutil.walk_packages(package.__path__, f"apps.{app_label}."): + if "serializer" not in name or ".tests" in name: + continue + module = importlib.import_module(name) + for attr in vars(module).values(): + if not inspect.isclass(attr): + continue + if not issubclass(attr, serializers.BaseSerializer): + continue + found[f"{attr.__module__}.{attr.__name__}"] = attr + return list(found.values()) + + +@pytest.mark.django_db +def test_every_nullable_text_column_forbids_the_empty_string() -> None: + """A column that can be NULL must not also be able to hold "". + + Without the CHECK constraint the column has two spellings of "unset" and + every reader has to test for both — which is the bug this rule exists to + prevent. The admin, management commands and the Xero sync all write past + serializer validation, so the database is the only place that can hold. + """ + with connection.cursor() as cursor: + cursor.execute( + "SELECT conrelid::regclass::text, conname FROM pg_constraint " + "WHERE contype = 'c' AND conname LIKE %s", + ["%_not_blank"], + ) + constrained = {(table, name) for table, name in cursor.fetchall()} + + missing = [ + f"{model._meta.db_table}.{column}" + for model, columns in _nullable_text_columns().items() + for column in columns.values() + if (model._meta.db_table, f"{column}_not_blank") not in constrained + ] + + assert not missing, ( + "Nullable text columns without a not-blank CHECK constraint — add one " + f"in a migration: {sorted(missing)}" + ) + + +def test_no_serializer_accepts_the_empty_string_for_a_nullable_column() -> None: + """A write of "" must fail as a 400, not reach the database as a 500. + + ``NullUnsetModelSerializer`` derives this from the model field, so a + failure here means either a serializer that bypasses that base or a field + declared explicitly with ``allow_blank=True``. + """ + nullable = _nullable_text_columns() + offenders: list[str] = [] + for serializer_class in _serializer_classes(): + model = getattr(getattr(serializer_class, "Meta", None), "model", None) + if model not in nullable: + continue + try: + fields = serializer_class().fields # type: ignore[attr-defined] # only Serializer subclasses expose .fields + except Exception: + continue # Serializers needing context cannot be bound bare. + offenders.extend( + f"{serializer_class.__module__}.{serializer_class.__name__}.{name}" + for name, field in fields.items() + if name in nullable[model] and getattr(field, "allow_blank", False) + ) + + assert not offenders, ( + 'Serializer fields accepting "" for a column whose only unset is NULL ' + f"— these would raise IntegrityError instead of returning 400: {sorted(offenders)}" + ) diff --git a/apps/workflow/tests/test_push_contacts.py b/apps/workflow/tests/test_push_contacts.py index 8d3a54237..568f77fe5 100644 --- a/apps/workflow/tests/test_push_contacts.py +++ b/apps/workflow/tests/test_push_contacts.py @@ -8,6 +8,8 @@ There is no prior test coverage for these functions; this is the first. """ +from datetime import datetime +from typing import TypedDict, Unpack from unittest.mock import patch from django.test import TestCase @@ -18,8 +20,17 @@ from apps.workflow.tests.fixtures.xero_responses import make_create_contacts_response -def _make_client(**overrides): - defaults = { +class _ClientOverrides(TypedDict, total=False): + name: str + email: str | None + address: str | None + xero_last_modified: datetime + xero_contact_id: str | None + phone: str | None + + +def _make_client(**overrides: Unpack[_ClientOverrides]) -> Company: + defaults: _ClientOverrides = { "name": "Acme Ltd", "email": "info@acme.test", "address": "123 Test Street", @@ -38,7 +49,7 @@ def _make_client(**overrides): return company -def _create_response(contact_id, name): +def _create_response(contact_id: str, name: str) -> Contacts: """Mirror what AccountingApi.create_contacts / update_contact returns: a Contacts wrapper holding a list of Contact SDK instances.""" return Contacts(contacts=[Contact(contact_id=contact_id, name=name)]) diff --git a/apps/workflow/tests/test_resource_version_middleware.py b/apps/workflow/tests/test_resource_version_middleware.py new file mode 100644 index 000000000..057ce9662 --- /dev/null +++ b/apps/workflow/tests/test_resource_version_middleware.py @@ -0,0 +1,50 @@ +"""OCC state tokens remain strong when response representations are gzipped.""" + +from django.http import HttpRequest, HttpResponse +from django.middleware.gzip import GZipMiddleware +from django.test import RequestFactory, SimpleTestCase + +from apps.workflow.middleware import ResourceVersionMiddleware + + +class ResourceVersionMiddlewareTests(SimpleTestCase): + def setUp(self) -> None: + self.request = RequestFactory().get("/", HTTP_ACCEPT_ENCODING="gzip") + + @staticmethod + def _response(etag: str) -> HttpResponse: + response = HttpResponse("compressible response " * 100) + response.headers["ETag"] = etag + return response + + def _process(self, etag: str) -> HttpResponse: + def view(_request: HttpRequest) -> HttpResponse: + return self._response(etag) + + preserve_version = ResourceVersionMiddleware(view) + gzip_response = GZipMiddleware(preserve_version) + response = gzip_response(self.request) + if not isinstance(response, HttpResponse): + raise TypeError("Synchronous middleware returned a non-HTTP response") + return response + + def test_preserves_job_and_po_versions_before_gzip_weakens_etag(self) -> None: + for strong_etag in ( + '"job:00000000-0000-0000-0000-000000000000:version"', + '"po:00000000-0000-0000-0000-000000000000:version"', + ): + with self.subTest(etag=strong_etag): + response = self._process(strong_etag) + + self.assertEqual(response.headers["Content-Encoding"], "gzip") + self.assertEqual(response.headers["ETag"], f"W/{strong_etag}") + self.assertEqual( + response.headers["X-Resource-Version"], + strong_etag, + ) + + def test_does_not_publish_unrelated_etag_as_a_resource_version(self) -> None: + response = self._process('"static-content-digest"') + + self.assertEqual(response.headers["ETag"], 'W/"static-content-digest"') + self.assertNotIn("X-Resource-Version", response.headers) diff --git a/apps/workflow/tests/test_search_telemetry.py b/apps/workflow/tests/test_search_telemetry.py index a08f07c6f..8af911039 100644 --- a/apps/workflow/tests/test_search_telemetry.py +++ b/apps/workflow/tests/test_search_telemetry.py @@ -50,7 +50,7 @@ def test_search_click_endpoint_records_generic_event(db): @pytest.mark.django_db -def test_search_telemetry_caps_displayed_results_at_100(): +def test_search_telemetry_caps_displayed_results_at_100() -> None: yielded_count = 0 def result_ids(): diff --git a/apps/workflow/tests/test_seed_xero_from_database.py b/apps/workflow/tests/test_seed_xero_from_database.py index e45192ba7..931a2ce39 100644 --- a/apps/workflow/tests/test_seed_xero_from_database.py +++ b/apps/workflow/tests/test_seed_xero_from_database.py @@ -10,20 +10,24 @@ """ from io import StringIO -from unittest.mock import patch +from types import SimpleNamespace +from unittest.mock import Mock, patch +from uuid import uuid4 from django.conf import settings from django.test import TestCase from django.utils import timezone from apps.company.models import Company +from apps.workflow.api.xero.transforms import sync_accounts from apps.workflow.management.commands.seed_xero_from_database import Command +from apps.workflow.models import XeroAccount SENTINEL_XERO_CONTACT_ID = "11111111-1111-1111-1111-111111111111" class ClearProductionXeroIdsTests(TestCase): - def test_refuses_when_db_name_ends_with_prod(self): + def test_refuses_when_db_name_ends_with_prod(self) -> None: """Catastrophic-regression guard: if the configured DB name ends with ``_prod``, ``clear_production_xero_ids`` must not touch the DB. Wiping live ``xero_contact_id``s breaks Xero @@ -44,3 +48,81 @@ def test_refuses_when_db_name_ends_with_prod(self): company.refresh_from_db() self.assertEqual(company.xero_contact_id, SENTINEL_XERO_CONTACT_ID) + + +class XeroAccountSeedTests(TestCase): + @patch( + "apps.workflow.api.xero.transforms.process_xero_data", + return_value={}, + ) + def test_sync_normalises_empty_optional_text( + self, _process_xero_data_mock: Mock + ) -> None: + sync_accounts( + [ + SimpleNamespace( + account_id=uuid4(), + code="", + name="Uncoded account", + description="", + type="", + tax_type="", + enable_payments_to_account=False, + _updated_date_utc=timezone.now(), + ) + ] + ) + + account = XeroAccount.objects.get(account_name="Uncoded account") + self.assertIsNone(account.account_code) + self.assertIsNone(account.description) + self.assertIsNone(account.account_type) + self.assertIsNone(account.tax_type) + + @patch( + "apps.workflow.management.commands.seed_xero_from_database.process_xero_data", + return_value={}, + ) + @patch( + "apps.workflow.management.commands.seed_xero_from_database.get_tenant_id", + return_value="demo-tenant", + ) + @patch("apps.workflow.management.commands.seed_xero_from_database.AccountingApi") + def test_empty_xero_description_is_stored_as_null( + self, + accounting_api_mock: Mock, + _tenant_id_mock: Mock, + _process_xero_data_mock: Mock, + ) -> None: + prod_xero_id = uuid4() + demo_xero_id = uuid4() + modified_at = timezone.now() + XeroAccount.objects.create( + xero_id=prod_xero_id, + account_code="805", + account_name="Accrued Liabilities", + description="Production description", + account_type="CURRLIAB", + tax_type="NONE", + xero_last_modified=modified_at, + raw_json={}, + ) + accounting_api_mock.return_value.get_accounts.return_value.accounts = [ + SimpleNamespace( + account_id=demo_xero_id, + code="805", + name="Accrued Liabilities", + description="", + type="CURRLIAB", + tax_type="NONE", + enable_payments_to_account=False, + _updated_date_utc=modified_at, + ) + ] + + command = Command() + command.process_accounts(dry_run=False) + + account = XeroAccount.objects.get(account_name="Accrued Liabilities") + self.assertEqual(account.xero_id, demo_xero_id) + self.assertIsNone(account.description) diff --git a/apps/workflow/tests/test_session_replay_api.py b/apps/workflow/tests/test_session_replay_api.py index 1b0d9fb0f..1f598cd9a 100644 --- a/apps/workflow/tests/test_session_replay_api.py +++ b/apps/workflow/tests/test_session_replay_api.py @@ -40,7 +40,7 @@ def workshop_staff(db): ) -def _client(user): +def _client(user: Staff) -> APIClient: api = APIClient() api.force_authenticate(user=user) return api diff --git a/apps/workflow/tests/test_sync_clients.py b/apps/workflow/tests/test_sync_clients.py index 350b4ae06..fba52e6ab 100644 --- a/apps/workflow/tests/test_sync_clients.py +++ b/apps/workflow/tests/test_sync_clients.py @@ -158,7 +158,7 @@ class SyncClientsArchivedContactTests(TestCase): the same name. sync_companies must handle both without crashing. """ - def setUp(self): + def setUp(self) -> None: self.active_xero_id = "9568adbc-aaaa-bbbb-cccc-000000000001" self.archived_xero_id = "17aa5e1e-aaaa-bbbb-cccc-000000000002" self.company_name = "Powder Coating Group NZ Limited" @@ -374,7 +374,7 @@ def test_resync_of_existing_number_is_grandfathered(self) -> None: self.assertEqual(created, []) owner_method.refresh_from_db() # Xero owns the number's existence only; the existing row is untouched. - self.assertEqual(owner_method.label, "") + self.assertIsNone(owner_method.label) def test_user_edited_label_and_primary_survive_resync(self) -> None: owner = self._client_with_phone("Phone Owner") diff --git a/apps/workflow/tests/test_xero_app_api.py b/apps/workflow/tests/test_xero_app_api.py index 65ef55f22..b52c1a713 100644 --- a/apps/workflow/tests/test_xero_app_api.py +++ b/apps/workflow/tests/test_xero_app_api.py @@ -3,6 +3,7 @@ import uuid from datetime import datetime, timedelta from datetime import timezone as dt_timezone +from typing import TypedDict, Unpack from unittest.mock import patch from rest_framework import status @@ -12,8 +13,22 @@ from apps.workflow.models import XeroApp -def _row(**overrides): - defaults = { +class _XeroAppOverrides(TypedDict, total=False): + label: str + client_id: str + client_secret: str + redirect_uri: str + is_active: bool + access_token: str | None + refresh_token: str | None + expires_at: datetime | None + day_remaining: int | None + minute_remaining: int | None + snapshot_at: datetime | None + + +def _row(**overrides: Unpack[_XeroAppOverrides]) -> XeroApp: + defaults: _XeroAppOverrides = { "label": "Primary", "client_id": "c-a", "client_secret": "s", @@ -24,7 +39,7 @@ def _row(**overrides): return XeroApp.objects.create(**defaults) -def _office_staff(email="office@example.test"): +def _office_staff(email: str = "office@example.test") -> Staff: return Staff.objects.create_user( email=email, password="x", @@ -35,11 +50,11 @@ def _office_staff(email="office@example.test"): class XeroAppApiPermissionTests(APITestCase): - def test_anonymous_forbidden_on_list(self): + def test_anonymous_forbidden_on_list(self) -> None: response = self.client.get("/api/workflow/xero-apps/") self.assertIn(response.status_code, (401, 403)) - def test_anonymous_forbidden_on_create(self): + def test_anonymous_forbidden_on_create(self) -> None: response = self.client.post( "/api/workflow/xero-apps/", { @@ -52,7 +67,7 @@ def test_anonymous_forbidden_on_create(self): ) self.assertIn(response.status_code, (401, 403)) - def test_non_office_staff_forbidden(self): + def test_non_office_staff_forbidden(self) -> None: worker = Staff.objects.create_user( email="worker@example.test", password="x", @@ -67,12 +82,12 @@ def test_non_office_staff_forbidden(self): class XeroAppApiListTests(APITestCase): - def setUp(self): + def setUp(self) -> None: self.client = APIClient() self.user = _office_staff() self.client.force_authenticate(self.user) - def test_list_returns_safe_fields_only(self): + def test_list_returns_safe_fields_only(self) -> None: expires = datetime.now(dt_timezone.utc) + timedelta(hours=1) _row( client_id="c-a", @@ -102,12 +117,12 @@ def test_list_returns_safe_fields_only(self): class XeroAppApiCreateTests(APITestCase): - def setUp(self): + def setUp(self) -> None: self.client = APIClient() self.user = _office_staff() self.client.force_authenticate(self.user) - def test_create_minimal_row(self): + def test_create_minimal_row(self) -> None: payload = { "label": "Backup", "client_id": "c-b", @@ -123,7 +138,7 @@ def test_create_minimal_row(self): self.assertEqual(row.client_secret, "s-b") self.assertEqual(row.webhook_key, "wk-b") - def test_create_duplicate_client_id_returns_400(self): + def test_create_duplicate_client_id_returns_400(self) -> None: _row(client_id="c-a") payload = { "label": "Dup", @@ -135,7 +150,7 @@ def test_create_duplicate_client_id_returns_400(self): response = self.client.post("/api/workflow/xero-apps/", payload, format="json") self.assertEqual(response.status_code, 400) - def test_create_without_secret_rejected(self): + def test_create_without_secret_rejected(self) -> None: # A row created without client_secret can never complete OAuth — # serializer must reject it on create even though secret is # write-only and otherwise tolerated as missing. @@ -150,7 +165,7 @@ def test_create_without_secret_rejected(self): self.assertIn("client_secret", response.data) self.assertFalse(XeroApp.objects.filter(client_id="c-no-secret").exists()) - def test_create_without_webhook_key_rejected(self): + def test_create_without_webhook_key_rejected(self) -> None: # A row without webhook_key would 401 every webhook delivery from # this app's tenant — symmetrical to the missing-client_secret # case, and just as fatal. @@ -165,7 +180,7 @@ def test_create_without_webhook_key_rejected(self): self.assertIn("webhook_key", response.data) self.assertFalse(XeroApp.objects.filter(client_id="c-no-hook").exists()) - def test_patch_without_secret_allowed(self): + def test_patch_without_secret_allowed(self) -> None: # PATCH must NOT require client_secret — the secret is already # persisted, and forcing operators to re-supply it on every label # tweak would be punitive. @@ -182,12 +197,12 @@ def test_patch_without_secret_allowed(self): class XeroAppApiPatchTests(APITestCase): - def setUp(self): + def setUp(self) -> None: self.client = APIClient() self.user = _office_staff() self.client.force_authenticate(self.user) - def test_patch_label_only_does_not_wipe_tokens(self): + def test_patch_label_only_does_not_wipe_tokens(self) -> None: expires = datetime.now(dt_timezone.utc) + timedelta(hours=1) row = _row( client_id="c-a", @@ -206,7 +221,7 @@ def test_patch_label_only_does_not_wipe_tokens(self): self.assertEqual(row.refresh_token, "rrr") self.assertEqual(row.day_remaining, 42) - def test_patch_client_id_wipes_tokens_and_quota(self): + def test_patch_client_id_wipes_tokens_and_quota(self) -> None: expires = datetime.now(dt_timezone.utc) + timedelta(hours=1) row = _row( client_id="c-a", @@ -230,7 +245,7 @@ def test_patch_client_id_wipes_tokens_and_quota(self): self.assertIsNone(row.day_remaining) self.assertIsNone(row.snapshot_at) - def test_patch_active_row_credentials_invalidates_singleton(self): + def test_patch_active_row_credentials_invalidates_singleton(self) -> None: # When the *active* row's credentials change, the in-process # ApiClient singleton is bound to the old client_id/secret. Without # a reset its next call would use stale credentials. Sibling @@ -253,7 +268,7 @@ def test_patch_active_row_credentials_invalidates_singleton(self): mock_reset.assert_called_once() mock_restart.assert_called_once() - def test_patch_inactive_row_credentials_does_not_restart(self): + def test_patch_inactive_row_credentials_does_not_restart(self) -> None: # PATCHing a non-active row's credentials must not touch the # singleton or restart workers — they're bound to the active row, # which is unchanged. @@ -275,7 +290,7 @@ def test_patch_inactive_row_credentials_does_not_restart(self): mock_reset.assert_not_called() mock_restart.assert_not_called() - def test_patch_label_only_does_not_restart(self): + def test_patch_label_only_does_not_restart(self) -> None: # Label change must not propagate as a credential rotation. row = _row(client_id="c-a", is_active=True) with ( @@ -297,18 +312,18 @@ def test_patch_label_only_does_not_restart(self): class XeroAppApiDeleteTests(APITestCase): - def setUp(self): + def setUp(self) -> None: self.client = APIClient() self.user = _office_staff() self.client.force_authenticate(self.user) - def test_delete_inactive_row_succeeds(self): + def test_delete_inactive_row_succeeds(self) -> None: row = _row(client_id="c-a", is_active=False) response = self.client.delete(f"/api/workflow/xero-apps/{row.id}/") self.assertEqual(response.status_code, 204) self.assertFalse(XeroApp.objects.filter(id=row.id).exists()) - def test_delete_active_row_refused(self): + def test_delete_active_row_refused(self) -> None: row = _row(client_id="c-a", is_active=True) response = self.client.delete(f"/api/workflow/xero-apps/{row.id}/") self.assertEqual(response.status_code, 400) @@ -316,12 +331,12 @@ def test_delete_active_row_refused(self): class XeroAppApiActivateTests(APITestCase): - def setUp(self): + def setUp(self) -> None: self.client = APIClient() self.user = _office_staff() self.client.force_authenticate(self.user) - def test_activate_swaps(self): + def test_activate_swaps(self) -> None: a = _row(client_id="c-a", is_active=True) b = _row(client_id="c-b", is_active=False) response = self.client.post(f"/api/workflow/xero-apps/{b.id}/activate/") @@ -331,6 +346,6 @@ def test_activate_swaps(self): self.assertFalse(a.is_active) self.assertTrue(b.is_active) - def test_activate_unknown_id_404(self): + def test_activate_unknown_id_404(self) -> None: response = self.client.post(f"/api/workflow/xero-apps/{uuid.uuid4()}/activate/") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/workflow/tests/test_xero_app_model.py b/apps/workflow/tests/test_xero_app_model.py index 63f12b18f..3964d8c68 100644 --- a/apps/workflow/tests/test_xero_app_model.py +++ b/apps/workflow/tests/test_xero_app_model.py @@ -5,15 +5,25 @@ marked is_active=True at a time. """ +from typing import TypedDict, Unpack + from django.db import IntegrityError, transaction from django.test import TestCase from apps.workflow.models import XeroApp +class _XeroAppOverrides(TypedDict, total=False): + label: str + client_id: str + client_secret: str + redirect_uri: str + is_active: bool + + class XeroAppModelTests(TestCase): - def _row(self, **overrides): - defaults = { + def _row(self, **overrides: Unpack[_XeroAppOverrides]) -> XeroApp: + defaults: _XeroAppOverrides = { "label": "Primary", "client_id": "abc123", "client_secret": "shh", @@ -23,7 +33,7 @@ def _row(self, **overrides): defaults.update(overrides) return XeroApp.objects.create(**defaults) - def test_create_minimal_row(self): + def test_create_minimal_row(self) -> None: row = self._row() self.assertEqual(row.label, "Primary") self.assertFalse(row.is_active) @@ -32,24 +42,24 @@ def test_create_minimal_row(self): self.assertIsNone(row.day_remaining) self.assertIsNone(row.snapshot_at) - def test_client_id_unique(self): + def test_client_id_unique(self) -> None: self._row(client_id="abc123", label="A") with self.assertRaises(IntegrityError): with transaction.atomic(): self._row(client_id="abc123", label="B") - def test_at_most_one_active_row(self): + def test_at_most_one_active_row(self) -> None: self._row(client_id="abc1", label="A", is_active=True) with self.assertRaises(IntegrityError): with transaction.atomic(): self._row(client_id="abc2", label="B", is_active=True) - def test_two_inactive_rows_allowed(self): + def test_two_inactive_rows_allowed(self) -> None: self._row(client_id="abc1", label="A", is_active=False) self._row(client_id="abc2", label="B", is_active=False) self.assertEqual(XeroApp.objects.count(), 2) - def test_can_swap_active_via_two_updates(self): + def test_can_swap_active_via_two_updates(self) -> None: # Real swaps go through swap_active() which clears the old active # row first, but the constraint must permit that pattern. a = self._row(client_id="abc1", label="A", is_active=True) diff --git a/apps/workflow/tests/test_xero_document_raw_json.py b/apps/workflow/tests/test_xero_document_raw_json.py index 0d2183346..74e0d44d6 100644 --- a/apps/workflow/tests/test_xero_document_raw_json.py +++ b/apps/workflow/tests/test_xero_document_raw_json.py @@ -33,7 +33,7 @@ def add_history_note_to_quote(self, external_id, note): class XeroDocumentRawJsonTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: self.client_obj = Company.objects.create( name="Raw JSON Company", xero_contact_id=str(uuid.uuid4()), @@ -54,7 +54,7 @@ def setUp(self): ) CompanyDefaults.clear_cache() - def test_created_invoice_stores_canonical_raw_json_dict(self): + def test_created_invoice_stores_canonical_raw_json_dict(self) -> None: raw_response = { "_contact": {"_name": "Raw JSON Company"}, "_invoice_id": str(uuid.uuid4()), @@ -95,7 +95,7 @@ def test_created_invoice_stores_canonical_raw_json_dict(self): self.assertEqual(invoice.total_incl_tax, Decimal("115.00")) self.assertEqual(invoice.amount_due, Decimal("115.00")) - def test_created_quote_stores_canonical_raw_json_dict(self): + def test_created_quote_stores_canonical_raw_json_dict(self) -> None: self.job.latest_quote.summary = {"cost": 0.0, "rev": 250.0, "hours": 0.0} self.job.latest_quote.save(update_fields=["summary"]) raw_response = { diff --git a/apps/workflow/tests/test_xero_instance_templates.py b/apps/workflow/tests/test_xero_instance_templates.py index 24e14c20c..d5a5f1e3c 100644 --- a/apps/workflow/tests/test_xero_instance_templates.py +++ b/apps/workflow/tests/test_xero_instance_templates.py @@ -1,4 +1,5 @@ import json +import os import subprocess import tempfile from decimal import Decimal @@ -10,11 +11,12 @@ from django.test import SimpleTestCase, TestCase from apps.accounts.models import Staff -from apps.crm.models import PhoneEndpoint +from apps.crm.models import PhoneEndpoint, PhoneProviderSettings from apps.workflow.models import CompanyDefaults, XeroApp REPO_ROOT = Path(__file__).resolve().parents[3] COMMON_SCRIPT = REPO_ROOT / "scripts" / "server" / "common.sh" +INSTANCE_SCRIPT = REPO_ROOT / "scripts" / "server" / "instance.sh" XERO_APPS_TEMPLATE = ( REPO_ROOT / "scripts" / "server" / "templates" / "xero-apps.json.template" ) @@ -135,9 +137,9 @@ def test_phone_provider_settings_template_renders_to_valid_json(self) -> None: "__PHONE_PROVIDER_BASE_URL_JSON__", '"http://phone-provider.lan"', ) - .replace("__PHONE_PROVIDER_USERNAME__", "phone-user") - .replace("__PHONE_PROVIDER_PASSWORD__", "phone-secret") - .replace("__PHONE_PROVIDER_ACCOUNT_CODE__", "15539090") + .replace("__PHONE_PROVIDER_USERNAME_JSON__", '"phone-user"') + .replace("__PHONE_PROVIDER_PASSWORD_JSON__", '"phone-secret"') + .replace("__PHONE_PROVIDER_ACCOUNT_CODE_JSON__", '"15539090"') ) payload = json.loads(rendered) @@ -237,3 +239,53 @@ def run(creds_file: str) -> "subprocess.CompletedProcess[str]": link = base / "linkdir" link.symlink_to(base) self.assertNotEqual(run(str(link / "inst.credentials.env")).returncode, 0) + + +class PhoneProviderReconfigureFixtureTests(TestCase): + def test_unconfigured_fixture_loads_with_null_credentials(self) -> None: + """Reconfigure must seed a scrubbed instance without violating constraints.""" + PhoneProviderSettings.objects.all().delete() + PhoneProviderSettings.get_solo() + + with tempfile.TemporaryDirectory() as instance_dir: + environment = os.environ.copy() + for variable in ( + "PHONE_PROVIDER_BASE_URL", + "PHONE_PROVIDER_USERNAME", + "PHONE_PROVIDER_PASSWORD", + "PHONE_PROVIDER_ACCOUNT_CODE", + "PHONE_PROVIDER_DOWNLOADS_ENABLED", + "PHONE_PROVIDER_RECORDING_DELETION_ENABLED", + ): + environment.pop(variable, None) + + subprocess.run( + [ + "bash", + "-c", + ( + 'source "$1"; ' + "log() { :; }; " + "chown() { :; }; " + 'render_phone_provider_settings_fixture "$2" ignored' + ), + str(INSTANCE_SCRIPT.parent / "test-harness"), + str(INSTANCE_SCRIPT), + instance_dir, + ], + check=True, + capture_output=True, + text=True, + env=environment, + ) + + fixture = Path(instance_dir) / ".fixtures" / "phone_provider_settings.json" + call_command("loaddata", str(fixture), verbosity=0) + + settings = PhoneProviderSettings.objects.get(pk=1) + self.assertFalse(settings.downloads_enabled) + self.assertFalse(settings.recording_deletion_enabled) + self.assertIsNone(settings.base_url) + self.assertIsNone(settings.username) + self.assertIsNone(settings.password) + self.assertIsNone(settings.account_code) diff --git a/apps/workflow/tests/test_xero_pay_item.py b/apps/workflow/tests/test_xero_pay_item.py index 62d4b5577..d107bcf8e 100644 --- a/apps/workflow/tests/test_xero_pay_item.py +++ b/apps/workflow/tests/test_xero_pay_item.py @@ -6,7 +6,7 @@ class XeroPayItemLookupTests(TestCase): - def test_get_by_multiplier_prefers_xero_backed_ordinary_time(self): + def test_get_by_multiplier_prefers_xero_backed_ordinary_time(self) -> None: stale = XeroPayItem.objects.create( name="Time and one half (old)", uses_leave_api=False, @@ -25,7 +25,7 @@ def test_get_by_multiplier_prefers_xero_backed_ordinary_time(self): self.assertEqual(XeroPayItem.get_by_multiplier(Decimal("1.00")), ordinary) self.assertNotEqual(XeroPayItem.get_by_multiplier(Decimal("1.00")), stale) - def test_get_by_multiplier_prefers_xero_backed_time_and_half(self): + def test_get_by_multiplier_prefers_xero_backed_time_and_half(self) -> None: XeroPayItem.objects.update_or_create( name="Overtime (1.5)", uses_leave_api=False, diff --git a/apps/workflow/tests/test_xero_po_manager.py b/apps/workflow/tests/test_xero_po_manager.py index d66930aa3..a2e31fdd3 100644 --- a/apps/workflow/tests/test_xero_po_manager.py +++ b/apps/workflow/tests/test_xero_po_manager.py @@ -29,7 +29,7 @@ class XeroPurchaseOrderManagerConstructionTests(BaseAPITestCase): @classmethod - def setUpTestData(cls): + def setUpTestData(cls) -> None: super().setUpTestData() cls.test_staff.is_office_staff = True cls.test_staff.save(update_fields=["is_office_staff"]) diff --git a/apps/workflow/tests/test_xero_quota_floor.py b/apps/workflow/tests/test_xero_quota_floor.py index 13f3c207f..3fba06a5f 100644 --- a/apps/workflow/tests/test_xero_quota_floor.py +++ b/apps/workflow/tests/test_xero_quota_floor.py @@ -12,10 +12,13 @@ (batches of 50) rather than per-item ``update_item`` calls. """ -from datetime import timedelta +from collections.abc import Iterator +from datetime import datetime, timedelta from decimal import Decimal from types import SimpleNamespace +from typing import TypedDict, Unpack from unittest.mock import MagicMock, patch +from uuid import UUID from django.core.cache import cache, caches from django.test import TestCase @@ -24,7 +27,7 @@ from apps.company.models import Company from apps.purchasing.models import Stock from apps.workflow.api.xero.client import quota_floor_breached -from apps.workflow.api.xero.sync import sync_xero_data +from apps.workflow.api.xero.sync import XeroSyncEvent, sync_xero_data from apps.workflow.exceptions import XeroQuotaFloorReached from apps.workflow.models import CompanyDefaults, XeroApp from apps.workflow.services.xero_sync_constants import SYNC_STATUS_KEY @@ -37,9 +40,22 @@ _shared = caches["shared"] -def _active_app(**overrides): +class _ActiveAppOverrides(TypedDict, total=False): + label: str + client_id: str + client_secret: str + redirect_uri: str + is_active: bool + access_token: str | None + refresh_token: str | None + day_remaining: int | None + minute_remaining: int | None + snapshot_at: datetime | None + + +def _active_app(**overrides: Unpack[_ActiveAppOverrides]) -> XeroApp: """Create an active XeroApp row with sensible defaults.""" - defaults = { + defaults: _ActiveAppOverrides = { "label": "Primary", "client_id": "test-c", "client_secret": "s", @@ -50,7 +66,9 @@ def _active_app(**overrides): return XeroApp.objects.create(**defaults) -def _set_quota(day_remaining, minute_remaining=60, snapshot_age_seconds=0): +def _set_quota( + day_remaining: int, minute_remaining: int = 60, snapshot_age_seconds: int = 0 +) -> None: """Set the active XeroApp's snapshot to specific values. Creates an active row if none exists, otherwise updates the existing one. @@ -70,7 +88,7 @@ def _set_quota(day_remaining, minute_remaining=60, snapshot_age_seconds=0): ) -def _set_company_floor(floor=100): +def _set_company_floor(floor: int = 100) -> None: CompanyDefaults.clear_cache() updated = CompanyDefaults.objects.filter(pk=1).update( xero_automated_day_floor=floor @@ -96,28 +114,28 @@ class QuotaFloorBreachedHelperTests(TestCase): useful sync; false negatives burn quota needed for user-triggered invoices. """ - def test_no_active_row_returns_false(self): + def test_no_active_row_returns_false(self) -> None: # Nothing to gate against — fall through and let the call go. self.assertFalse(quota_floor_breached(100)) - def test_missing_snapshot_returns_false(self): + def test_missing_snapshot_returns_false(self) -> None: # Active row exists but no API call has happened yet. _active_app() self.assertFalse(quota_floor_breached(100)) - def test_day_remaining_above_floor_returns_false(self): + def test_day_remaining_above_floor_returns_false(self) -> None: _set_quota(day_remaining=200) self.assertFalse(quota_floor_breached(100)) - def test_day_remaining_at_floor_returns_true(self): + def test_day_remaining_at_floor_returns_true(self) -> None: _set_quota(day_remaining=100) self.assertTrue(quota_floor_breached(100)) - def test_day_remaining_below_floor_returns_true(self): + def test_day_remaining_below_floor_returns_true(self) -> None: _set_quota(day_remaining=50) self.assertTrue(quota_floor_breached(100)) - def test_day_remaining_none_returns_false(self): + def test_day_remaining_none_returns_false(self) -> None: # First-call response sometimes omits day_remaining. _active_app( day_remaining=None, @@ -126,7 +144,7 @@ def test_day_remaining_none_returns_false(self): ) self.assertFalse(quota_floor_breached(100)) - def test_stale_snapshot_returns_false(self): + def test_stale_snapshot_returns_false(self) -> None: # Snapshot older than the staleness window — let the next call # probe Xero fresh; the rolling 24h window has freed quota since. _set_quota(day_remaining=10, snapshot_age_seconds=60 * 60) @@ -141,8 +159,13 @@ class RateLimit429WritesToActiveRowTests(TestCase): moment of write.""" def _run_handle_rate_limit( - self, *, app_id, day_remaining, minute_remaining, problem - ): + self, + *, + app_id: UUID, + day_remaining: int, + minute_remaining: int, + problem: str, + ) -> None: from apps.workflow.api.xero.client import RateLimitedRESTClient # Build a minimal fake exception that matches what the SDK passes in. @@ -166,7 +189,7 @@ def _run_handle_rate_limit( # day-limit branch raises; we don't care about that here. pass - def test_minute_limit_429_writes_snapshot_to_bound_row(self): + def test_minute_limit_429_writes_snapshot_to_bound_row(self) -> None: row = _active_app() with patch("apps.workflow.api.xero.client.time.sleep"): self._run_handle_rate_limit( @@ -180,7 +203,7 @@ def test_minute_limit_429_writes_snapshot_to_bound_row(self): self.assertEqual(row.minute_remaining, 0) self.assertIsNotNone(row.snapshot_at) - def test_day_limit_429_writes_snapshot_and_last_429_at(self): + def test_day_limit_429_writes_snapshot_and_last_429_at(self) -> None: row = _active_app() with patch( "apps.workflow.api.xero.client.persist_app_error", @@ -196,7 +219,7 @@ def test_day_limit_429_writes_snapshot_and_last_429_at(self): self.assertEqual(row.day_remaining, 0) self.assertIsNotNone(row.last_429_at) - def test_writes_to_bound_row_not_active_row(self): + def test_writes_to_bound_row_not_active_row(self) -> None: # Construct a client bound to row B's id while row A is currently # active. The 429 must update B, not A. a = _active_app(client_id="c-a", label="A", is_active=True) @@ -229,7 +252,7 @@ class StoreQuotaSnapshotWritesToBoundRowTests(TestCase): healthy quota while the active worker is actually exhausted. """ - def test_2xx_updates_bound_row(self): + def test_2xx_updates_bound_row(self) -> None: from apps.workflow.api.xero.client import RateLimitedRESTClient row = _active_app() @@ -242,7 +265,7 @@ def test_2xx_updates_bound_row(self): self.assertEqual(row.minute_remaining, 55) self.assertIsNotNone(row.snapshot_at) - def test_no_app_id_silently_skips(self): + def test_no_app_id_silently_skips(self) -> None: from apps.workflow.api.xero.client import RateLimitedRESTClient # Pre-existing row stays untouched when client has no app_id. @@ -254,7 +277,7 @@ def test_no_app_id_silently_skips(self): row.refresh_from_db() self.assertIsNone(row.day_remaining) - def test_both_none_leaves_stored_values_untouched(self): + def test_both_none_leaves_stored_values_untouched(self) -> None: # Token refreshes hit identity.xero.com, which returns no quota # headers — the heartbeat runs every 5 min, so a clobber here means # the badge reads "—" almost all the time. @@ -274,7 +297,7 @@ def test_both_none_leaves_stored_values_untouched(self): self.assertEqual(row.minute_remaining, 58) self.assertEqual(row.snapshot_at, before) - def test_partial_reading_updates_only_present_field(self): + def test_partial_reading_updates_only_present_field(self) -> None: # A minute-limit 429 carries X-MinLimit-Remaining but not the day # header — record the minute count without wiping the day count. from apps.workflow.api.xero.client import RateLimitedRESTClient @@ -301,14 +324,14 @@ class SynchroniseXeroDataOrchestratorGateTests(TestCase): must abort as an operational abort, not report success after doing no work. """ - def setUp(self): + def setUp(self) -> None: cache.delete("xero_sync_lock") _set_company_floor() - def tearDown(self): + def tearDown(self) -> None: cache.delete("xero_sync_lock") - def test_floor_breached_raises_before_pipeline(self): + def test_floor_breached_raises_before_pipeline(self) -> None: # The orchestrator must raise XeroQuotaFloorReached, NOT yield a # warning event and return. A returned generator looks like a # successful empty stream to XeroSyncService.run_sync, which would @@ -334,7 +357,7 @@ def test_floor_breached_raises_before_pipeline(self): mock_deep.assert_not_called() mock_normal.assert_not_called() - def test_floor_breached_does_not_advance_last_sync_timestamps(self): + def test_floor_breached_does_not_advance_last_sync_timestamps(self) -> None: # Business case: a quota abort must not advance last-sync timestamps; # otherwise the next real sync skips work and stale Xero data remains # hidden for days. @@ -356,16 +379,16 @@ class SyncXeroDataPerPageGateTests(TestCase): it consumes quota reserved for user-initiated work. """ - def setUp(self): + def setUp(self) -> None: # Snapshot now lives on XeroApp rows — cleared automatically by # TestCase's per-test transaction rollback. _set_company_floor() - def _consume(self, generator): + def _consume(self, generator: Iterator[XeroSyncEvent]) -> list[XeroSyncEvent]: """Fully iterate the generator and return its emitted events.""" return list(generator) - def test_floor_breached_raises_before_fetch(self): + def test_floor_breached_raises_before_fetch(self) -> None: # Per-page gate must raise (not yield+return). See the orchestrator # test above for the same reasoning re: silent success masking. _set_quota(day_remaining=50) @@ -386,7 +409,7 @@ def test_floor_breached_raises_before_fetch(self): fetch.assert_not_called() self.assertIn("quota at floor", str(ctx.exception)) - def test_above_floor_proceeds_normally(self): + def test_above_floor_proceeds_normally(self) -> None: _set_quota(day_remaining=500) # One page returning fewer than page_size → loop completes after one fetch. page = MagicMock() @@ -419,7 +442,7 @@ class StockBatchedUpsertRefactorTests(TestCase): per item while still preserving the create-vs-update behavior Xero expects. """ - def setUp(self): + def setUp(self) -> None: _set_company_floor() # sync_all_local_stock_to_xero builds AccountingApi from the lazy # auth.api_client singleton, which reads the active XeroApp row on @@ -430,12 +453,12 @@ def setUp(self): auth._reset_api_client() - def tearDown(self): + def tearDown(self) -> None: from apps.workflow.api.xero import auth auth._reset_api_client() - def test_upsert_batch_called_with_mixed_create_and_update_payload(self): + def test_upsert_batch_called_with_mixed_create_and_update_payload(self) -> None: from apps.workflow.api.xero import stock_sync # Two local items: one already in Xero by Code, one brand new. @@ -532,13 +555,13 @@ class RunSyncAbortedBranchTests(TestCase): operational abort, not as a green sync or as pages of duplicate AppErrors. """ - def setUp(self): + def setUp(self) -> None: _shared.delete(SYNC_STATUS_KEY) - def tearDown(self): + def tearDown(self) -> None: _shared.delete(SYNC_STATUS_KEY) - def test_quota_floor_emits_aborted_marker_and_skips_persist_app_error(self): + def test_quota_floor_emits_aborted_marker_and_skips_persist_app_error(self) -> None: from apps.workflow.models import AppError from apps.workflow.tasks import xero_sync_task diff --git a/apps/workflow/tests/test_xero_setup_command.py b/apps/workflow/tests/test_xero_setup_command.py index a33ff0cd4..2dc131bff 100644 --- a/apps/workflow/tests/test_xero_setup_command.py +++ b/apps/workflow/tests/test_xero_setup_command.py @@ -2,6 +2,7 @@ from decimal import Decimal from pathlib import Path from types import SimpleNamespace +from typing import TypedDict, Unpack from unittest.mock import Mock, patch from uuid import UUID @@ -13,22 +14,28 @@ from apps.workflow.models import XeroPayItem +class _PayItemOverrides(TypedDict, total=False): + name: str + multiplier: Decimal | None + xero_id: str | None + + class EnsureDemoXeroItemsExistTests(TestCase): - def _earnings_names(self): + def _earnings_names(self) -> list[str]: return list( XeroPayItem.objects.filter(uses_leave_api=False) .order_by("name") .values_list("name", flat=True) ) - def _leave_names(self): + def _leave_names(self) -> list[str]: return list( XeroPayItem.objects.filter(uses_leave_api=True) .order_by("name") .values_list("name", flat=True) ) - def _earnings_item(self, **overrides): + def _earnings_item(self, **overrides: Unpack[_PayItemOverrides]) -> XeroPayItem: name = overrides.pop("name", "Time and one half") item = XeroPayItem.objects.get(name=name, uses_leave_api=False) item.multiplier = overrides.pop("multiplier", Decimal("1.50")) @@ -38,7 +45,7 @@ def _earnings_item(self, **overrides): item.save() return item - def _leave_item(self, **overrides): + def _leave_item(self, **overrides: Unpack[_PayItemOverrides]) -> XeroPayItem: name = overrides.pop("name", "Annual Leave") item = XeroPayItem.objects.get(name=name, uses_leave_api=True) item.multiplier = overrides.pop("multiplier", None) @@ -297,7 +304,7 @@ def test_production_setup_validates_without_creating_xero_items( ], ) - def test_removed_create_missing_xero_items_flag_is_rejected(self): + def test_removed_create_missing_xero_items_flag_is_rejected(self) -> None: parser = Command().create_parser("manage.py", "xero") with self.assertRaises(CommandError): parser.parse_args(["--setup", "--create-missing-xero-items"]) diff --git a/apps/workflow/tests/test_xero_tenant_id.py b/apps/workflow/tests/test_xero_tenant_id.py index 282ec3112..9be5ddcd0 100644 --- a/apps/workflow/tests/test_xero_tenant_id.py +++ b/apps/workflow/tests/test_xero_tenant_id.py @@ -25,14 +25,14 @@ def _active_xero_app() -> None: class XeroTenantIdTests(BaseTestCase): - def setUp(self): + def setUp(self) -> None: cache.delete(TENANT_ID_CACHE_KEY) company_defaults = CompanyDefaults.get_solo() company_defaults.xero_tenant_id = "configured-tenant" company_defaults.save(update_fields=["xero_tenant_id"]) _active_xero_app() - def test_get_tenant_id_uses_company_defaults_on_cache_miss(self): + def test_get_tenant_id_uses_company_defaults_on_cache_miss(self) -> None: """Normal Xero calls must not discover the stable tenant over the network.""" from apps.workflow.api.xero.auth import get_tenant_id @@ -43,7 +43,7 @@ def test_get_tenant_id_uses_company_defaults_on_cache_miss(self): assert cache.get(TENANT_ID_CACHE_KEY) == "configured-tenant" mock_identity_api.assert_not_called() - def test_get_tenant_id_uses_cache_before_company_defaults(self): + def test_get_tenant_id_uses_cache_before_company_defaults(self) -> None: """Active-app swaps own cache invalidation; ordinary reads should trust cache.""" from apps.workflow.api.xero.auth import get_tenant_id diff --git a/apps/workflow/tests/test_xero_transform_contact_resolution.py b/apps/workflow/tests/test_xero_transform_contact_resolution.py index 09a961451..c559123d3 100644 --- a/apps/workflow/tests/test_xero_transform_contact_resolution.py +++ b/apps/workflow/tests/test_xero_transform_contact_resolution.py @@ -19,7 +19,7 @@ def _make_contact(contact_id: str, **extra): class XeroTransformContactResolutionTests(TestCase): - def setUp(self): + def setUp(self) -> None: self.company = Company.objects.create( name="Existing Company", xero_contact_id="contact-123", diff --git a/apps/workflow/tests/test_xero_webhooks.py b/apps/workflow/tests/test_xero_webhooks.py index d55434469..39512f5d0 100644 --- a/apps/workflow/tests/test_xero_webhooks.py +++ b/apps/workflow/tests/test_xero_webhooks.py @@ -257,14 +257,14 @@ def test_no_webhook_key_returns_503_and_persists_app_error(self) -> None: mock_delay.assert_not_called() def test_no_webhook_key_with_blank_rows_still_returns_503(self) -> None: - # XeroApp rows exist but all have webhook_key="" — same failure + # XeroApp rows exist but none has a webhook_key — same failure # mode as no rows at all. XeroApp.objects.create( label="Blank", client_id="blank", client_secret="x", redirect_uri="https://e/cb", - webhook_key="", + webhook_key=None, ) body = json.dumps({"events": [_event()]}).encode("utf-8") request = self.factory.post( diff --git a/apps/workflow/views/data_versions_view.py b/apps/workflow/views/data_versions_view.py index deb836e16..08516d28b 100644 --- a/apps/workflow/views/data_versions_view.py +++ b/apps/workflow/views/data_versions_view.py @@ -25,6 +25,7 @@ from apps.accounts.models import Staff from apps.company.models import Company, CompanyPersonLink, Person from apps.crm.models import PhoneCallRecord, PhoneCallRecording +from apps.job.kanban_version import KanbanDatasetVersion from apps.job.models import Job from apps.purchasing.models import Stock from apps.workflow.services.error_persistence import persist_app_error @@ -41,12 +42,24 @@ def _stock_version() -> str: def _kanban_version() -> str: - # Tracks inputs read by KanbanService.serialize_job_for_api() plus column - # membership/order. This is deliberately conservative: false positives - # only trigger a reload, while false negatives would serve stale cards. + """Track Job rows for incremental Kanban card reconciliation.""" + aggregate = Job.objects.aggregate( + updated=Max("updated_at"), + created=Max("created_at"), + count=Count("id"), + ) + version = KanbanDatasetVersion.from_values( + updated_at=aggregate["updated"], + created_at=aggregate["created"], + count=aggregate["count"], + ) + return version.encode() + + +def _kanban_related_version() -> str: + """Conservatively track related display inputs used by Kanban cards.""" return "|".join( [ - _model_version(Job, "updated_at"), _model_version(Company, "django_updated_at"), _model_version(CompanyPersonLink, "updated_at"), _model_version(Person, "updated_at"), @@ -73,6 +86,7 @@ def _crm_calls_version() -> str: DATASET_VERSION_PROVIDERS: Dict[str, Callable[[], str]] = { "stock": _stock_version, "kanban": _kanban_version, + "kanban_related": _kanban_related_version, "crm_calls": _crm_calls_version, } diff --git a/apps/workflow/views/session_replay_view.py b/apps/workflow/views/session_replay_view.py index b3ce21cd1..409d2996a 100644 --- a/apps/workflow/views/session_replay_view.py +++ b/apps/workflow/views/session_replay_view.py @@ -114,7 +114,7 @@ def post(self, request): serializer.is_valid(raise_exception=True) recording = create_recording( user=request.user, - user_agent=request.headers.get("User-Agent", ""), + user_agent=request.headers.get("User-Agent") or None, **serializer.validated_data, ) return Response( @@ -214,8 +214,8 @@ def post(self, request): "source": "frontend", }, app="frontend", - file=data.get("file") or "", - function=data.get("function") or "", + file=data.get("file") or None, + function=data.get("function") or None, user_id=request.user.id, session_replay=recording, ) diff --git a/apps/workflow/views/xero/xero_quote_manager.py b/apps/workflow/views/xero/xero_quote_manager.py index 82194a704..ac8a71073 100644 --- a/apps/workflow/views/xero/xero_quote_manager.py +++ b/apps/workflow/views/xero/xero_quote_manager.py @@ -96,7 +96,7 @@ def get_line_items(self, breakdown: bool = True) -> list[DocumentLineItem]: for cl in latest_quote.cost_lines.all(): line_items.append( DocumentLineItem( - description=sanitize_for_xero(cl.desc), + description=sanitize_for_xero(cl.desc or ""), quantity=Decimal(str(cl.quantity)), unit_amount=Decimal(str(cl.unit_rev)), account_code=self._get_account_code(), diff --git a/apps/workflow/xero_webhooks.py b/apps/workflow/xero_webhooks.py index c630e6192..b0ab31faf 100644 --- a/apps/workflow/xero_webhooks.py +++ b/apps/workflow/xero_webhooks.py @@ -33,7 +33,7 @@ def validate_webhook_signature(request: HttpRequest) -> bool: emitted it. During credential rotation an install has two registered apps in Xero's portal — each with its own signing key — and both apps emit webhooks until the operator deletes the old one. So we - accept the request if any non-blank XeroApp.webhook_key produces a + accept the request if any non-NULL XeroApp.webhook_key produces a matching HMAC. If a now-inactive app keeps firing webhooks because the operator hasn't deleted it in the Xero portal, that's fine: we process them. Cleaning up orphan apps in Xero is the operator's job. @@ -50,7 +50,9 @@ def validate_webhook_signature(request: HttpRequest) -> bool: return False keys = list( - XeroApp.objects.exclude(webhook_key="").values_list("webhook_key", flat=True) + XeroApp.objects.exclude(webhook_key__isnull=True).values_list( + "webhook_key", flat=True + ) ) if not keys: exc = RuntimeError( @@ -63,6 +65,8 @@ def validate_webhook_signature(request: HttpRequest) -> bool: body = request.body for key in keys: + if key is None: + continue # A XeroApp with no webhook key cannot verify anything. expected_signature_bytes = hmac.new( key.encode("utf-8"), body, hashlib.sha256 ).digest() diff --git a/docketworks/settings.py b/docketworks/settings.py index 4804c4d3e..16bf3a454 100644 --- a/docketworks/settings.py +++ b/docketworks/settings.py @@ -202,6 +202,8 @@ def validate_required_settings() -> None: "django.middleware.security.SecurityMiddleware", "apps.workflow.middleware.DisallowedHostMiddleware", "django.middleware.gzip.GZipMiddleware", + # Runs on the response before the outer gzip middleware weakens ETag. + "apps.workflow.middleware.ResourceVersionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", diff --git a/docs/adr/0029-servers-run-the-production-branch.md b/docs/adr/0029-servers-run-the-production-branch.md index 9a92893bb..9b309af98 100644 --- a/docs/adr/0029-servers-run-the-production-branch.md +++ b/docs/adr/0029-servers-run-the-production-branch.md @@ -1,29 +1,37 @@ -# 0029 — Servers run the production branch +# 0029 — Separate integration from production releases -Servers deploy `production` by default; `main` is the integration branch, released by an explicit merge from `main` into `production` and verified on UAT as a candidate (`deploy.sh --ref`) beforehand. +`main` represents integrated work; `production` represents released work. ## Problem -Merging to `main` and deploying to servers were the same event: `deploy.sh` resolved `origin/main`, so anything merged was immediately what the next deploy shipped. A production bug could not be patched without also shipping every unrelated change that had landed on `main` since the last deploy, and long-running structural work (renames, migration squashes) sat between a diagnosed prod bug and its fix. +`main` used to represent both integrated work and the production release. A +production fix therefore also shipped every unrelated change merged since the +previous release. ## Decision -Servers only ever run the `production` branch. All feature PRs target `main` as before. A release is a promotion PR merging `main` → `production`, followed by deploy to UAT, verification, then deploy to prod. A hotfix is a branch cut from `production`, merged into `production` by PR, deployed, and back-merged to `main` immediately so no fix exists only on `production`. Nothing is ever pushed directly to `production`, and no server is ever pointed at any other ref except transiently via `deploy.sh --ref` for candidate verification on UAT. +Feature PRs target `main`. Testing and UAT servers typically track `main`; +production servers typically track `production`. After UAT verification, a +release PR promotes `main` to `production`. + +A hotfix branches from `production`, merges back by PR, deploys, and is +immediately back-merged to `main`. ## Why -Separating "integrated" from "released" makes the deployable state an explicit, deliberate ref instead of a side effect of merge timing. Prod can be patched from exactly what prod runs, regardless of what is in flight on `main`; `main` can absorb large structural work without freezing releases. The promotion merge is also the natural audit point: the diff of the promotion PR is precisely what the fleet is about to receive. Keeping every server (prod, UAT, demo) on the same branch preserves UAT/prod parity — UAT verifies the very ref prod will get, not an approximation. +Separating integrated work from released work lets production be patched +independently of unreleased changes. Each release is also an explicit, +reviewable promotion. ## Alternatives considered -- **Deploy `main` everywhere (status quo):** simplest possible model and fine while the project was one instance with continuous deploys. Wrong once multiple paying instances need patching independently of integration velocity. -- **Release tags instead of a branch:** deploy pinned tags (`prod-YYYY-MM-DD-`). Auditable, but hotfixes need a branch anyway, "latest release" becomes a convention rather than a ref, and every tool that today asks "what should servers run?" needs tag-resolution logic. A branch is the same thing with a stable name. -- **Per-instance release pinning:** each instance records its own ref. Maximum flexibility, but it institutionalises fleet drift; the shared-release directory design (one SHA, many instances) deliberately pushes the other way. +- **Deploy `main` to production:** simpler, but couples production fixes to all + integrated work. +- **Release tags:** auditable, but hotfixes still need a branch and "latest + release" becomes a convention rather than a stable ref. ## Consequences -- Releasing gains one explicit step: the `main` → `production` promotion PR. Deploys themselves are unchanged (`deploy.sh` now resolves `origin/production` by default). -- Hotfixes must be back-merged to `main` in the same working session; a fix that exists only on `production` is a regression waiting to be re-released. +- Releasing gains one explicit step: the `main` → `production` promotion PR. +- Hotfixes must be back-merged to `main` immediately. - `production` carries the same branch protections as `main`. -- Anything that assumes servers track `main` (docs, boot-time catch-up units, operator habit) must say `production` instead. -- `deploy.sh --ref` and `instance.sh create --ref` deploy a candidate ref (e.g. `origin/main`) to UAT; `instance.sh status ` reports which tracked ref a running instance matches. A non-production ref on a `*-prod` instance is refused unless acknowledged — interactive confirm, or `--allow-prod-ref` non-interactively. Nothing persists the ref per instance, so boot-time catch-up still returns UAT to `production`. diff --git a/docs/adr/0033-version-constraints-record-tested-versions.md b/docs/adr/0033-version-constraints-record-tested-versions.md new file mode 100644 index 000000000..72e384468 --- /dev/null +++ b/docs/adr/0033-version-constraints-record-tested-versions.md @@ -0,0 +1,25 @@ +# 0033 — Version constraints record what passed testing, not what is compatible + +Every declared dependency bound is a record of the newest version that passed our tests, so a release sweep ignores the bounds and re-tests at latest. + +## Problem + +`pillow = "^12.1.1"`, `holidays = ">=0.93,<0.101"`, and `prettier: "3.8.3"` all look like compatibility limits — as though 13, 0.101, and 3.9 were tried and rejected. They were not. Each records the newest version available the last time we swept dependencies. Reading them as compatibility boundaries makes an upgrade look risky when nobody has ever tested the newer version, and a sweep that respects them can only ever move within the last sweep's ceiling. + +## Decision + +Treat every version constraint as a test record, never as evidence of incompatibility. During a release dependency sweep, ignore the declared bounds: widen each constraint to the newest published version, re-lock, and run the gates. A version stays behind latest only when something external forces it — an upstream package's own published metadata pins it, or the upgrade fails a gate. Record that reason in the PR. When a constraint is widened and the gates pass, the new bound becomes the updated test record. + +## Why + +The bound and the reason for the bound are different facts, and only the bound is written down. Once they are conflated, dependency debt compounds silently: each sweep respects the previous sweep's ceiling, so majors accumulate untested behind carets that were never a judgement about anything. Testing is the only thing that produces real evidence, and it is cheap and automated here — so the honest default is to move everything to latest and let the gates speak. That also keeps the expensive signal, an actual failure, distinguishable from the absence of signal. + +## Alternatives considered + +- **Conservative carets, upgrade on demand:** the common default — take patches automatically, majors only when a feature needs one. Sensible where upgrades are risky or the test suite is thin. Here it guarantees that majors are only ever discovered under deadline, bundled with the feature that forced them, which is the worst time to debug a breaking change. +- **Pin everything exactly and never sweep:** maximum reproducibility, and correct for a system that must not change. It converts every eventual upgrade into an archaeology exercise across years of releases at once, and leaves security advisories unpatched by default. +- **Automated per-PR bumps only (dependabot merged as it opens them):** keeps the lag near zero without a sweep. It cannot resolve interlocked upgrades — the cases where a bump is only possible alongside another package's — which is exactly where the debt collects. + +## Consequences + +Dependency lag stays near zero and majors surface one at a time, on a schedule, with the whole test suite as the arbiter. The cost is a real testing burden every release, and sweeps that occasionally have to back a version out. A constraint below latest becomes meaningful: it means something genuinely blocked, and the PR says what. diff --git a/docs/adr/README.md b/docs/adr/README.md index 927f4cb71..b49d67196 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -38,7 +38,8 @@ See [`_template.md`](_template.md). Copy, renumber, fill in. | 0026 | Plan the tests before the plan is approved | | 0027 | A capability deploys with the means to operate it | | 0028 | Type annotations are data contracts | -| 0029 | Servers run the production branch | +| 0029 | Separate integration from production releases | | 0030 | First-class People and Company links | | 0031 | One logging gate: the debug library with namespaces | | 0032 | Less code is better: prefer libraries over homegrown implementations | +| 0033 | Version constraints record what passed testing, not what is compatible | diff --git a/docs/updating.md b/docs/updating.md index 204d60368..2a7755750 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -28,10 +28,11 @@ If you're running the application locally for development: ## Server Environment (Multi-Tenant) -**This section is the deploy runbook.** Servers only ever run the `production` -branch (ADR 0029): feature PRs merge to `main`, and a release is a promotion PR -merging `main` → `production` (hotfixes PR straight into `production` and are -back-merged to `main`). PR merged to `production`? SSH into the server and run: +**This section is the production deploy runbook.** Feature PRs merge to `main` +and are tested on UAT. After verification, a release PR promotes `main` to +`production`, which production servers typically track (ADR 0029). Hotfixes +merge into `production` and are back-merged to `main`. PR merged to `production`? +SSH into the server and run: ```bash # One client diff --git a/docs/urls/company.md b/docs/urls/company.md index f47a5bdaa..fe3feba4e 100644 --- a/docs/urls/company.md +++ b/docs/urls/company.md @@ -44,7 +44,6 @@ | URL Pattern | View | Name | Description | |-------------|------|------|-------------| | `//jobs/` | `company_rest_views.CompanyJobsRestView` | `companies:company_jobs_rest` | REST view for fetching all jobs for a specific company. | -| `/jobs//person/` | `company_rest_views.JobPersonRestView` | `companies:job_person_rest` | REST view for person information operations for a job. | ### Other | URL Pattern | View | Name | Description | diff --git a/docs/urls/job.md b/docs/urls/job.md index e7b9b6fdb..3047a1f2c 100644 --- a/docs/urls/job.md +++ b/docs/urls/job.md @@ -52,6 +52,7 @@ | `/jobs//files/` | `job_files_collection_view.JobFilesCollectionView` | `jobs:job_files_collection` | Collection operations on job files. | | `/jobs//files//` | `job_file_detail_view.JobFileDetailView` | `jobs:job_file_detail` | Resource operations on individual job files. | | `/jobs//files//thumbnail/` | `job_file_thumbnail_view.JobFileThumbnailView` | `jobs:job_file_thumbnail` | Thumbnail serving for job files. | +| `/jobs//finish/` | `job_rest_views.JobFinishRestView` | `jobs:job_finish_rest` | Finish Job workspace: the authoritative customer balance and checklist. | | `/jobs//header/` | `job_rest_views.JobHeaderRestView` | `jobs:job_header_rest` | REST view for Job header information. | | `/jobs//invoices/` | `job_rest_views.JobInvoicesRestView` | `jobs:job_invoices_rest` | REST view for Job invoices. | | `/jobs//labour-rates/` | `labour_views.JobLabourRatesView` | `jobs:job_labour_rates_rest` | Read or update a job's per-subtype charge-out rates. | @@ -78,6 +79,7 @@ | `/jobs/fetch-all/` | `kanban_view_api.FetchAllJobsAPIView` | `jobs:api_fetch_all_jobs` | Fetch all jobs for Kanban board - API endpoint. | | `/jobs/fetch-by-column//` | `kanban_view_api.FetchJobsByColumnAPIView` | `jobs:api_fetch_jobs_by_column` | Fetch jobs by kanban column using new categorization system. | | `/jobs/fetch//` | `kanban_view_api.FetchJobsAPIView` | `jobs:api_fetch_jobs` | Fetch jobs by status with optional search - API endpoint. | +| `/jobs/kanban-changes/` | `kanban_view_api.KanbanChangesAPIView` | `jobs:api_kanban_changes` | Return changed Kanban cards after an opaque Job dataset version. | | `/jobs/status-choices/` | `job_rest_views.JobStatusChoicesRestView` | `jobs:job_status_choices_rest` | REST view for Job status choices. | | `/jobs/status-values/` | `kanban_view_api.FetchStatusValuesAPIView` | `jobs:api_fetch_status_values` | Return available status values for Kanban - API endpoint. | | `/jobs/weekly-metrics/` | `job_rest_views.WeeklyMetricsRestView` | `jobs:weekly_metrics_rest` | REST view for fetching weekly metrics. | diff --git a/frontend/docs/example_conversation.md b/frontend/docs/example_conversation.md deleted file mode 100644 index 67f2ef2ee..000000000 --- a/frontend/docs/example_conversation.md +++ /dev/null @@ -1,110 +0,0 @@ -# Example Estimator Conversation – End-to-End Walkthrough - -This shows a realistic, disciplined quoting interaction where the estimator drives input, and the system confirms each step before proceeding. - ---- - -## 💬 Input Phase - -**Estimator:** -We need 3 boxes, 700×700×400, brushed stainless. Welded seams, open top. - -**System:** -You've entered: - -- 3 × box -- Dimensions: 700×700×400 mm -- Material: brushed stainless - -❓ Confirm: Is this 304/4, 1.2 mm gauge? -(If not, please specify.) - -**Estimator:** -Yes, 304/4, 1.2 mm. - -**System:** -✅ Material confirmed. -📦 Welded 5-face box, open top. Folded seam allowances will be applied. - -Proceeding to material usage calculation. - ---- - -## 📐 Material Calculation Phase - -**System:** -Unfolded layout: 4 sides + base = 5 faces -Each face = 700×400 mm = 0.28 m² -Total per box (before waste): 5 × 0.28 = 1.4 m² -Applying 12% waste = 1.4 × 1.12 = **1.57 m² per box** -Total for 3 boxes = **4.71 m²** - -🔢 Method 1: Waste % → 4.71 m² ÷ 2.88 = 1.64 sheets - -🔟 Method 2: Tenths-based - -- Standard sheet = 2400×1200 = 10 tenths = 240×600 mm each -- Each part needs 6 tenths (700 mm spans width) → 3 × 6 = 18 tenths → 1.8 sheets - -📏 Method 3: Proportional Billing - -- Align 700 mm to 1200 side → 700/2400 = 29.1% per sheet -- 3 × 29.1% = 87.3% of a sheet → 0.873 sheets (for billing only) - -📝 All methods agree on ~1.7–1.8 sheets used. - -❓ Confirm to proceed with sheet pricing lookup? - -**Estimator:** -Confirmed. Proceed. - ---- - -## 💰 Supplier Lookup Phase - -**System:** -Checking pricing via MCP tools... - -**Supplier options:** -| Supplier | Sheet Type | Price | Code | Delivery | Notes | -|----------|----------------|-------|-------------|----------|-------------| -| Rivtec | 304/4 1.2 mm | $198 | RVT-SS30412 | 2 days | In stock | -| EDL | 304/4 1.2 mm | $236 | EDL-S30412 | 3 days | | - -📌 Cheapest: Rivtec ($198) -⚠️ Large price gap → please confirm supplier choice - -**Estimator:** -Stick with Rivtec. Confirmed. - ---- - -## 🛠️ Labor Estimation Phase - -**System:** -Labor template: welded stainless box, 5 sides, 700 mm - -- Cutting: 0.2 hrs/box -- Folding: 0.3 hrs/box -- Welding: 0.4 hrs/box -- Finishing: 0.25 hrs/box -- Total = 1.15 hrs/box × 3 = **3.45 hrs** - -Hourly rate: $95 → Labor cost = **$327.75** - ---- - -## 📦 Final Quote Output - -| Item | Qty | Rate | Cost | Notes | -| ----------- | ---- | ---- | ------- | ---------------------- | -| Sheet metal | 2 | $198 | $396.00 | 1.8 → rounded to 2 | -| Labor | 3.45 | $95 | $327.75 | Welded box fabrication | -| Total | | | $723.75 | | - -Markup: 20% -Quote total (excl. GST): **$868.50** - -✅ Ready for export or further edits. - ---- diff --git a/frontend/docs/jobview-etag-guide.md b/frontend/docs/jobview-etag-guide.md index 4a7e903d7..49f14415b 100644 --- a/frontend/docs/jobview-etag-guide.md +++ b/frontend/docs/jobview-etag-guide.md @@ -151,7 +151,7 @@ Testing checklist Change log - ETag enforcement and conditional GET added in views and services; see references above. -- 412 PreconditionFailed mapped in Base view; see BaseJobRestView.handle_service_error(). +- 412 PreconditionFailedError mapped in Base view; see BaseJobRestView.handle_service_error(). - Additional defensive checks in service prevent a person from leaking across unrelated companies after an update. --- diff --git a/frontend/docs/quoting_tool_testing.md b/frontend/docs/quoting_tool_testing.md deleted file mode 100644 index 50108e38c..000000000 --- a/frontend/docs/quoting_tool_testing.md +++ /dev/null @@ -1,364 +0,0 @@ -# Quoting Tool Testing Strategy - -## Testing Philosophy - -The quoting tool requires rigorous testing because: - -- **Financial accuracy** - Wrong calculations cost money -- **Complex business logic** - Multiple calculation methods must agree -- **MCP integration** - External data sources need mocking -- **Estimator workflow** - UX must be tested end-to-end - -## Test Structure - -### 1. Unit Tests (Phase 1 - Foundation) - -#### Material Calculation Logic - -```typescript -// tests/unit/useMaterialCalculation.test.ts -describe('Material Calculation', () => { - describe('calculateAreaWithWaste', () => { - it('calculates simple box area correctly', () => { - const spec = { - type: 'sheet', - dimensions: { length: 700, width: 700, height: 400 }, - quantity: 3, - } - const result = calculateAreaWithWaste(spec, 12) - expect(result).toBe(4.71) // 1.4 * 1.12 * 3 - }) - }) - - describe('calculateTenthsUsage', () => { - it('counts tenths for standard sheet layout', () => { - const spec = { - /* 700x700 part */ - } - const result = calculateTenthsUsage(spec) - expect(result).toBe(18) // 6 tenths × 3 parts - }) - }) - - describe('proportional billing vs tenths', () => { - it('flags large discrepancies between methods', () => { - const usage = calculateAllMethods(testSpec) - const variance = Math.abs(usage.tenthsUsed - usage.proportionalBilling) - expect(variance).toBeLessThan(0.5) // Methods should agree within 0.5 sheets - }) - }) -}) -``` - -#### Input Parsing - -```typescript -// tests/unit/inputParser.test.ts -describe('Input Parser', () => { - it('parses welded box description', () => { - const input = '3 boxes, 700×700×400, brushed stainless. Welded seams, open top.' - const result = parseEstimatorInput(input) - - expect(result.quantity).toBe(3) - expect(result.material).toBe('304/4') - expect(result.fabricationMethod).toBe('welding') - }) - - it('normalizes material shorthand', () => { - expect(normalizeMaterial('brushed')).toBe('304/4') - expect(normalizeMaterial('mild steel')).toBe('mild-steel') - }) - - it('flags ambiguous inputs', () => { - const input = 'some boxes, stainless steel' - const validation = validateInput(parseEstimatorInput(input)) - - expect(validation.isValid).toBe(false) - expect(validation.missingFields).toContain('quantity') - expect(validation.missingFields).toContain('dimensions') - }) -}) -``` - -### 2. Integration Tests (Phase 2 - MCP Integration) - -#### MCP Service Mocking - -```typescript -// tests/integration/mcpService.test.ts -import { setupMCPMock } from '../mocks/mcpMock' - -describe('MCP Integration', () => { - beforeEach(() => { - setupMCPMock({ - supplierPrices: [ - { supplier: 'Rivtec', price: 198, partNumber: 'RVT-SS30412' }, - { supplier: 'EDL', price: 236, partNumber: 'EDL-S30412' }, - ], - remnantStock: null, - }) - }) - - it('fetches supplier prices via MCP', async () => { - const prices = await QuotingToolService.getInstance().fetchSupplierPrices(materialSpec) - - expect(prices).toHaveLength(2) - expect(prices[0].supplier).toBe('Rivtec') - expect(prices[0].unitPrice).toBe(198) - }) - - it('flags large price gaps', async () => { - setupMCPMock({ - supplierPrices: [ - { supplier: 'A', price: 100 }, - { supplier: 'B', price: 200 }, // 100% gap - ], - }) - - const prices = await fetchSupplierPrices(materialSpec) - expect(prices.warnings).toContain('Large price gap detected') - }) -}) -``` - -### 3. End-to-End Tests (Phase 3 - Complete Workflow) - -#### Full Quote Generation - -```typescript -// tests/e2e/quoteGeneration.test.ts -describe('Complete Quote Generation', () => { - it('generates accurate quote for welded box example', async () => { - // Use exact example from documentation - const input = '3 boxes, 700×700×400, brushed stainless. Welded seams, open top.' - - const quote = await generateCompleteQuote(input) - - // Verify material usage - expect(quote.materialUsage.totalSheets).toBe(2) // Rounded up from 1.8 - expect(quote.materialUsage.areaWithWaste).toBeCloseTo(4.71) - - // Verify pricing - expect(quote.lineItems[0].description).toContain('Sheet metal') - expect(quote.lineItems[0].totalPrice).toBe(396) // 2 × $198 - - // Verify labor - const laborItem = quote.lineItems.find((item) => item.type === 'labor') - expect(laborItem.totalPrice).toBeCloseTo(327.75) // 3.45 hrs × $95 - - // Verify total - expect(quote.totalQuote).toBeCloseTo(868.5) // With 20% markup - }) - - it('handles quote checking mode', async () => { - const originalInput = '3 boxes, 700×700×400, brushed stainless' - const manualQuote = { - /* manual quote data */ - } - - const comparison = await compareQuotes(originalInput, manualQuote) - - expect(comparison.discrepancies).toHaveLength(0) // Should match - }) -}) -``` - -### 4. Component Tests (Phase 4 - UI Testing) - -#### Vue Component Testing - -```typescript -// tests/components/EstimatorInput.test.ts -import { mount } from '@vue/test-utils' -import EstimatorInput from '@/components/quoting-tool/EstimatorInput.vue' - -describe('EstimatorInput Component', () => { - it('validates input before proceeding', async () => { - const wrapper = mount(EstimatorInput) - - await wrapper.find('textarea').setValue('incomplete input') - await wrapper.find('button[type="submit"]').trigger('click') - - expect(wrapper.emitted('validation-failed')).toBeTruthy() - expect(wrapper.emitted('input-confirmed')).toBeFalsy() - }) - - it('shows confirmation dialog with parsed data', async () => { - const wrapper = mount(EstimatorInput) - - await wrapper.find('textarea').setValue('3 boxes, 700×700×400, 304/4') - await wrapper.find('button[type="submit"]').trigger('click') - - expect(wrapper.find('.confirmation-dialog').exists()).toBe(true) - expect(wrapper.text()).toContain('3 × box') - expect(wrapper.text()).toContain('700×700×400') - }) -}) -``` - -## Test Data Management - -### Golden Test Cases - -Create standardized test cases based on real jobs: - -```typescript -// tests/fixtures/goldenTestCases.ts -export const GOLDEN_CASES = [ - { - name: 'Welded Stainless Box', - input: '3 boxes, 700×700×400, brushed stainless. Welded seams, open top.', - expectedOutput: { - materialUsage: { - areaWithWaste: 4.71, - tenthsUsed: 1.8, - proportionalBilling: 0.873, - totalSheets: 2, - }, - totalQuote: 868.5, - }, - }, - { - name: 'Mild Steel Plate', - input: '10 plates, 500×300×3mm, mild steel', - expectedOutput: { - /* ... */ - }, - }, -] -``` - -### MCP Mock Data - -```typescript -// tests/mocks/mcpMock.ts -export const setupMCPMock = (config: MCPMockConfig) => { - // Mock MCP server responses - vi.mock('@/services/mcp', () => ({ - fetchSupplierPrices: vi.fn().mockResolvedValue(config.supplierPrices), - checkRemnantStock: vi.fn().mockResolvedValue(config.remnantStock), - calculateLabor: vi.fn().mockResolvedValue(config.laborRates), - })) -} -``` - -## Test Automation Strategy - -### CI/CD Pipeline - -```yaml -# .github/workflows/quoting-tool-tests.yml -name: Quoting Tool Tests -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install dependencies - run: npm ci - - - name: Run unit tests - run: npm run test:unit -- --coverage - - - name: Run integration tests - run: npm run test:integration - - - name: Run E2E tests - run: npm run test:e2e - - - name: Upload coverage - uses: codecov/codecov-action@v3 -``` - -### Test Scripts - -```json -// package.json -{ - "scripts": { - "test:unit": "vitest run src/**/*.test.ts", - "test:integration": "vitest run tests/integration/**/*.test.ts", - "test:e2e": "vitest run tests/e2e/**/*.test.ts", - "test:golden": "vitest run tests/golden/**/*.test.ts", - "test:watch": "vitest watch", - "test:coverage": "vitest run --coverage" - } -} -``` - -## Development Testing Workflow - -### 1. TDD for New Features - -```typescript -// 1. Write failing test first -describe('new calculation method', () => { - it('calculates tube bending allowance', () => { - const result = calculateTubeBending(spec) - expect(result.bendAllowance).toBe(expectedValue) - }) -}) - -// 2. Implement minimum code to pass -// 3. Refactor while keeping tests green -``` - -### 2. Regression Testing - -- Golden test cases run on every commit -- Any change to calculation logic requires approval -- Version control for test data and expected outputs - -### 3. Manual Testing Checklist - -```markdown -## Pre-Release Testing Checklist - -### Basic Functionality - -- [ ] Can parse simple job descriptions -- [ ] Material calculations agree across all 3 methods -- [ ] Supplier pricing displays correctly -- [ ] Quote totals are mathematically correct - -### Edge Cases - -- [ ] Handles missing dimensions gracefully -- [ ] Validates material specifications -- [ ] Reports MCP service failures clearly -- [ ] Large quantities don't break calculations - -### Integration - -- [ ] MCP queries return real data -- [ ] Quote export works in target format -- [ ] Performance acceptable with complex jobs -``` - -## Performance Testing - -### Load Testing - -```typescript -// tests/performance/loadTest.ts -describe('Performance Tests', () => { - it('handles complex multi-part jobs within 2 seconds', async () => { - const complexJob = generateComplexJob(50) // 50 different parts - - const startTime = Date.now() - const result = await generateCompleteQuote(complexJob) - const duration = Date.now() - startTime - - expect(duration).toBeLessThan(2000) // Must complete within 2 seconds - expect(result.lineItems).toHaveLength(50) - }) -}) -``` - -This testing strategy ensures the quoting tool is reliable, accurate, and maintainable while providing confidence for financial calculations. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33ab94c38..65a056b47 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,7 +21,7 @@ "@types/uuid": "^11.0.0", "@unovis/vue": "^1.5.2", "@vee-validate/zod": "^4.15.1", - "@vueuse/core": "^13.9.0", + "@vueuse/core": "^14.3.0", "@zodios/core": "^10.9.6", "axios": "^1.16.1", "class-variance-authority": "^0.7.1", @@ -36,8 +36,8 @@ "lodash-es": "^4.18.1", "lucide-vue-next": "^1.0.0", "openapi-zod-client": "^1.18.3", - "pdf-vue3": "^1.0.12", - "pinia": "^3.0.1", + "pdf-vue3": "^2.0.1", + "pinia": "^4.0.2", "qs": "^6.15.2", "quill": "^2.0.3", "reka-ui": "^2.8.2", @@ -62,7 +62,7 @@ "@types/debug": "^4.1.13", "@types/dompurify": "^3.2.0", "@types/js-cookie": "^3.0.6", - "@types/node": "^25.9.3", + "@types/node": "^26.1.2", "@typescript-eslint/eslint-plugin": "^8.59.4", "@typescript-eslint/parser": "^8.64.0", "@vitejs/plugin-vue": "^6.0.8", @@ -74,12 +74,12 @@ "codesight": "^1.18.0", "dotenv": "^17.4.2", "eslint": "^10.7.0", - "eslint-plugin-vue": "~10.8.0", + "eslint-plugin-vue": "~10.10.0", "happy-dom": "^20.8.9", "jiti": "^2.4.2", "lint-staged": "^17.0.3", "npm-run-all2": "^8.0.4", - "prettier": "3.8.3", + "prettier": "3.9.6", "puppeteer": "^25.0.2", "tsx": "^4.21.0", "typescript": "~6.0.3", @@ -102,19 +102,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", + "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.15", @@ -129,6 +120,8 @@ }, "node_modules/@apidevtools/openapi-schemas": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", "license": "MIT", "engines": { "node": ">=10" @@ -136,6 +129,8 @@ }, "node_modules/@apidevtools/swagger-methods": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { @@ -155,42 +150,6 @@ "openapi-types": ">=7" } }, - "node_modules/@apidevtools/swagger-parser/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@apidevtools/swagger-parser/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@apidevtools/swagger-parser/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -206,26 +165,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -240,8 +203,16 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -277,11 +248,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -292,6 +265,8 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -353,23 +328,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -452,18 +431,22 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.6", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -600,9 +583,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "peer": true, "engines": { @@ -655,23 +638,23 @@ } }, "node_modules/@docsearch/css": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz", - "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.3.tgz", + "integrity": "sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==", "dev": true, "license": "MIT" }, "node_modules/@docsearch/js": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-4.6.2.tgz", - "integrity": "sha512-qj1yoxl3y4GKoK7+VM6fq/rQqPnvUmg3IKzJ9x0VzN14QVzdB/SG/J6VfV1BWT5RcPUFxIcVwoY1fwHM2fSRRw==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-4.6.3.tgz", + "integrity": "sha512-qUIX2b4Apew3tv4F0qhmgShsl/Lfw4m6mqv/5/5dWNxwTcDdLMp2s3YwZ+NMGh3IKCg0pBaXm7Q5VdyU5Rj+cQ==", "dev": true, "license": "MIT" }, "node_modules/@docsearch/sidepanel-js": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/sidepanel-js/-/sidepanel-js-4.6.2.tgz", - "integrity": "sha512-Pni85AP/GwRj7fFg8cBJp0U04tzbueBvWSd3gysgnOsVnQVSZwSYncfErUScLE1CAtR+qocPDFjmYR9AMRNJtQ==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/sidepanel-js/-/sidepanel-js-4.6.3.tgz", + "integrity": "sha512-grGSmvXzG0if+mrzdIKykvpIAuEQ9u0sEJ2eLRRCaQfJvsWqh2C2/aY04bIzWvDh7myi5rvl8D+tUNsVrjYQ3A==", "dev": true, "license": "MIT" }, @@ -726,23 +709,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@emotion/cache": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", @@ -828,9 +794,9 @@ "peer": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -844,9 +810,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -860,9 +826,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -876,9 +842,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -892,9 +858,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -908,9 +874,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -924,9 +890,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -940,9 +906,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -956,9 +922,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -972,9 +938,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -988,9 +954,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -1004,9 +970,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -1020,9 +986,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -1036,9 +1002,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -1052,9 +1018,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -1068,9 +1034,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -1084,9 +1050,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -1100,9 +1066,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -1116,9 +1082,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -1132,9 +1098,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -1148,9 +1114,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -1164,9 +1130,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -1180,9 +1146,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -1196,9 +1162,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -1212,9 +1178,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -1228,9 +1194,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -1244,9 +1210,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1287,49 +1253,10 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/config-array/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1377,35 +1304,45 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.1", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.1", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.1", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@floating-ui/vue": { - "version": "1.1.6", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.11.tgz", + "integrity": "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0", - "@floating-ui/utils": "^0.2.9", + "@floating-ui/dom": "^1.7.6", + "@floating-ui/utils": "^0.2.11", "vue-demi": ">=0.13.0" } }, "node_modules/@floating-ui/vue/node_modules/vue-demi": { "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -1429,48 +1366,56 @@ } }, "node_modules/@fontsource-variable/inter": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", - "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" } }, "node_modules/@humanfs/core": { - "version": "0.19.1", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1483,6 +1428,8 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1494,9 +1441,9 @@ } }, "node_modules/@iconify-json/simple-icons": { - "version": "1.2.79", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.79.tgz", - "integrity": "sha512-aNyO7Fd1qej9oQfIyohYFRv0lhQLaZ+6UkK1c1qwax0MDPUOZOdq65MlU500kow97pD/W+b2u1And3e25eE24Q==", + "version": "1.2.92", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.92.tgz", + "integrity": "sha512-hR0ozxR97t1dzWw+esoxFijZ15gagt7EIgF3CNifu2yICXhS7gnun4Y+j+odJQtNSl7wvqMdoLbViIShwe/fdw==", "dev": true, "license": "CC0-1.0", "dependencies": { @@ -1520,7 +1467,9 @@ } }, "node_modules/@internationalized/number": { - "version": "3.6.3", + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", + "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -1528,6 +1477,8 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -1542,54 +1493,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1612,6 +1515,8 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1619,6 +1524,8 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1633,6 +1540,8 @@ }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, "node_modules/@juggle/resize-observer": { @@ -1678,9 +1587,9 @@ } }, "node_modules/@kodeglot/vue-calendar/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -1692,24 +1601,14 @@ }, "node_modules/@liuli-util/fs-extra": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@liuli-util/fs-extra/-/fs-extra-0.1.0.tgz", + "integrity": "sha512-eaAyDyMGT23QuRGbITVY3SOJff3G9ekAAyGqB9joAnTBmqvFN+9a1FazOdO70G6IUqgpKV451eBHYSRcOJ/FNQ==", "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.13", "fs-extra": "^10.1.0" } }, - "node_modules/@liuli-util/fs-extra/node_modules/fs-extra": { - "version": "10.1.0", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -1725,12 +1624,13 @@ } }, "node_modules/@mapbox/jsonlint-lines-primitives": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", - "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.3.tgz", + "integrity": "sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==", + "license": "MIT", "peer": true, "engines": { - "node": ">= 0.6" + "node": ">= 22" } }, "node_modules/@mapbox/mapbox-gl-supported": { @@ -1748,9 +1648,9 @@ "peer": true }, "node_modules/@mapbox/tiny-sdf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.1.0.tgz", - "integrity": "sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==", "license": "BSD-2-Clause", "peer": true }, @@ -1801,6 +1701,8 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -1813,6 +1715,8 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -1821,6 +1725,8 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -1833,13 +1739,15 @@ }, "node_modules/@one-ini/wasm": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "dev": true, "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.138.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", - "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1847,6 +1755,8 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -1855,30 +1765,32 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/pkgr" } }, "node_modules/@playwright/test": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", - "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.0.tgz", + "integrity": "sha512-9zOJ6ZQRAena31MpOH9VSzIz8Ou3YJ/wtY/eQm5T2uhfhG7/U3COrMS8xOtUrZrp9OgdmzEnIYODye3nY1VqzA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.60.0" + "playwright": "1.62.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@polka/url": { @@ -1889,17 +1801,14 @@ "license": "MIT" }, "node_modules/@puppeteer/browsers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.2.tgz", - "integrity": "sha512-JnOSHrAdCQOj27P5QnTrd6bkYd9cXXeFMJS5UJF3UmQbpZQAMMO7AaL0NyrT7i2l/43bwjaHguU+LOpBRyx66w==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.6.tgz", + "integrity": "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.3", - "progress": "^2.0.3", - "semver": "^7.7.4", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" + "modern-tar": "^0.7.6", + "yargs": "^18.0.0" }, "bin": { "browsers": "lib/main-cli.js" @@ -1908,18 +1817,22 @@ "node": ">=22.12.0" }, "peerDependencies": { - "proxy-agent": ">=8.0.1" + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" }, "peerDependenciesMeta": { "proxy-agent": { "optional": true + }, + "yauzl": { + "optional": true } } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", - "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -1933,9 +1846,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", - "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -1949,9 +1862,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", - "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -1965,9 +1878,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", - "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -1981,9 +1894,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", - "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -1997,9 +1910,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", - "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -2013,9 +1926,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", - "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -2029,9 +1942,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", - "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -2045,9 +1958,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", - "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -2061,9 +1974,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", - "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -2077,9 +1990,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", - "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -2093,9 +2006,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", - "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -2109,9 +2022,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", - "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -2127,9 +2040,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", - "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -2143,9 +2056,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", - "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -2165,9 +2078,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.3.tgz", + "integrity": "sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==", "cpu": [ "arm" ], @@ -2179,9 +2092,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.3.tgz", + "integrity": "sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==", "cpu": [ "arm64" ], @@ -2193,9 +2106,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.3.tgz", + "integrity": "sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==", "cpu": [ "arm64" ], @@ -2207,9 +2120,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.3.tgz", + "integrity": "sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==", "cpu": [ "x64" ], @@ -2221,9 +2134,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.3.tgz", + "integrity": "sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==", "cpu": [ "arm64" ], @@ -2235,9 +2148,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.3.tgz", + "integrity": "sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==", "cpu": [ "x64" ], @@ -2249,9 +2162,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.3.tgz", + "integrity": "sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==", "cpu": [ "arm" ], @@ -2263,9 +2176,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.3.tgz", + "integrity": "sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==", "cpu": [ "arm" ], @@ -2277,9 +2190,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.3.tgz", + "integrity": "sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==", "cpu": [ "arm64" ], @@ -2291,9 +2204,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.3.tgz", + "integrity": "sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==", "cpu": [ "arm64" ], @@ -2305,9 +2218,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.3.tgz", + "integrity": "sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==", "cpu": [ "loong64" ], @@ -2319,9 +2232,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.3.tgz", + "integrity": "sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==", "cpu": [ "loong64" ], @@ -2333,9 +2246,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.3.tgz", + "integrity": "sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==", "cpu": [ "ppc64" ], @@ -2347,9 +2260,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.3.tgz", + "integrity": "sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==", "cpu": [ "ppc64" ], @@ -2361,9 +2274,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.3.tgz", + "integrity": "sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==", "cpu": [ "riscv64" ], @@ -2375,9 +2288,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.3.tgz", + "integrity": "sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==", "cpu": [ "riscv64" ], @@ -2389,9 +2302,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.3.tgz", + "integrity": "sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==", "cpu": [ "s390x" ], @@ -2403,9 +2316,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.3.tgz", + "integrity": "sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==", "cpu": [ "x64" ], @@ -2417,9 +2330,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.3.tgz", + "integrity": "sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==", "cpu": [ "x64" ], @@ -2431,9 +2344,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.3.tgz", + "integrity": "sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==", "cpu": [ "x64" ], @@ -2445,9 +2358,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.3.tgz", + "integrity": "sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==", "cpu": [ "arm64" ], @@ -2459,9 +2372,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.3.tgz", + "integrity": "sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==", "cpu": [ "arm64" ], @@ -2473,9 +2386,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.3.tgz", + "integrity": "sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==", "cpu": [ "ia32" ], @@ -2487,9 +2400,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.3.tgz", + "integrity": "sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==", "cpu": [ "x64" ], @@ -2501,9 +2414,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.3.tgz", + "integrity": "sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==", "cpu": [ "x64" ], @@ -2515,46 +2428,46 @@ ] }, "node_modules/@rrweb/packer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@rrweb/packer/-/packer-2.1.0.tgz", - "integrity": "sha512-M3Di2TuUN2xqgwK6OiLgikB0ebU1Wxx6iXMoSiDVhDZRgyc6euL5JQs3GclJOuYstDx+8jjbZ12hTcQe+Ai8WQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rrweb/packer/-/packer-2.1.1.tgz", + "integrity": "sha512-xPGT7/Cfn1yHD2MOwQhX/8yTvNculurUxPT6ZKXVW8Wh/LI7Jn19L+e8PYKFhj/M89uwNeoTr6hiUQxgb8SMzQ==", "license": "MIT", "dependencies": { - "@rrweb/types": "^2.1.0", + "@rrweb/types": "^2.1.1", "fflate": "^0.4.4" } }, "node_modules/@rrweb/record": { - "version": "2.0.0-alpha.20", - "resolved": "https://registry.npmjs.org/@rrweb/record/-/record-2.0.0-alpha.20.tgz", - "integrity": "sha512-marVOU3Lc285mwLCp+vI6M/eJ+wZ6lcTa7fX0zcdmBsM+j5loXmMQjkmIvkji5+lAbkq5/w2cwto3GS0TaCjtg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rrweb/record/-/record-2.1.1.tgz", + "integrity": "sha512-c2u175R+daC37PhbjaQV0migxtIXsLuD0mRgRstj/x/CRmLGctRlDi373/4miIUclleq4eZIQXAglH9opclFRQ==", "license": "MIT", "dependencies": { - "@rrweb/types": "^2.0.0-alpha.20", - "@rrweb/utils": "^2.0.0-alpha.20", - "rrweb": "^2.0.0-alpha.20" + "@rrweb/types": "^2.1.1", + "@rrweb/utils": "^2.1.1", + "rrweb": "^2.1.1" } }, "node_modules/@rrweb/replay": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@rrweb/replay/-/replay-2.1.0.tgz", - "integrity": "sha512-a6s3Lzf4iIEL22o6+DHZrbjH/q6OIM9CFh/KzU6pDZ2mOYFP3u3Yd3IL57Oc9usnqFt/kNCvOjUf5JbBrQ9o7A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rrweb/replay/-/replay-2.1.1.tgz", + "integrity": "sha512-jq64eyJvrh/ioBS1MpDrs/Lj+7QY2TW4xGxp4g4LJKdltSkqMjMbUmhTDcpzaEDZt66S0gasGcgqCulsksSXnw==", "license": "MIT", "dependencies": { - "@rrweb/types": "^2.1.0", - "rrweb": "^2.1.0" + "@rrweb/types": "^2.1.1", + "rrweb": "^2.1.1" } }, "node_modules/@rrweb/types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.1.0.tgz", - "integrity": "sha512-svOHkcuukP6rIjz2umwVsS7uYct+2h0N97+bg+8IJYKIUj5+YjwIvqx5y9NILFKPIu3yk+Mb+KDFfJ8RV+4JlA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.1.1.tgz", + "integrity": "sha512-g0I3nCNL1S7slDXwhunxOuOoswkY8WVZJQrPFiwixOc6xoD514d1JLTuyAzCKIox8g/PaLKtV5g31Uk42inQSQ==", "license": "MIT" }, "node_modules/@rrweb/utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.1.0.tgz", - "integrity": "sha512-hU7MT3TSdD+Do7FmMoHrBfPevFCheN5vo2mn97Y4vbXuwemopAmo8dqo5KJeMaA6oQQt7FinHJ0Xqb7k68Ci6Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.1.1.tgz", + "integrity": "sha512-x2SgJAD3YJ9eVcLZ5l6OqWMtczdA3VfS5XqPeqpZdQgV9oh6Id5GK2cDN4bimnkqryOcIJdz8cTvV0yNvqQ+nA==", "license": "MIT" }, "node_modules/@shikijs/core": { @@ -2650,7 +2563,9 @@ "license": "MIT" }, "node_modules/@swc/helpers": { - "version": "0.5.17", + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -2669,53 +2584,47 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" + "tailwindcss": "4.3.3" } }, - "node_modules/@tailwindcss/node/node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" - }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -2725,13 +2634,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -2741,13 +2650,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -2757,13 +2666,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -2773,13 +2682,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -2789,13 +2698,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], @@ -2805,13 +2714,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], @@ -2821,13 +2730,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], @@ -2837,13 +2746,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], @@ -2853,13 +2762,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2874,75 +2783,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.7.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.7.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -2952,13 +2807,13 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -2968,667 +2823,162 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", - "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, - "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" - }, "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz", + "integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==", "license": "MIT", "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" + "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", - "jiti": "^2.7.0", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", "license": "MIT", "engines": { - "node": ">= 20" + "node": ">=12" }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", - "cpu": [ - "arm64" - ], + "node_modules/@tanstack/virtual-core": { + "version": "3.17.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.6.tgz", + "integrity": "sha512-h0/Ebo18CkOrChlQIhNtQkM5ySUnh/GumQ/D1st3hG2HWUPEF+ILUc2k29UtivCi/9G7w7G3/f7Xyd5cCFbKBw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", - "cpu": [ - "x64" - ], + "node_modules/@tanstack/vue-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.3.tgz", + "integrity": "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, "engines": { - "node": ">= 20" + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": ">=3.2" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", - "cpu": [ - "x64" - ], + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.34", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.34.tgz", + "integrity": "sha512-CBqbCcnVsKpl9IJ7frPnbBmAqmc7JttSySg04kgLYJ4yYuC8JsAbtUHP0yLtWGLyByjihN3SwaDCgHQ+Z4iPoA==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "@tanstack/virtual-core": "3.17.6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } + "node_modules/@tsconfig/node22": { + "version": "22.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.5.tgz", + "integrity": "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==", + "dev": true, + "license": "MIT" }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } + "node_modules/@tsconfig/svelte": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-1.0.13.tgz", + "integrity": "sha512-5lYJP45Xllo4yE/RUBccBT32eBlRDbqN8r1/MIvQbKxW3aFqaYPCNgm8D5V20X4ShHcwvYWNlKg3liDh1MlBoA==", + "license": "MIT" }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", - "cpu": [ - "arm64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", - "cpu": [ - "x64" - ], + "node_modules/@types/adm-zip": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.8.tgz", + "integrity": "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", - "cpu": [ - "x64" - ], + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "license": "MIT" + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.11.1", - "@emnapi/runtime": "^1.11.1", - "@emnapi/wasi-threads": "^1.2.2", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/vite/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tanstack/table-core": { - "version": "8.21.3", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.13.10", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/vue-table": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.3.tgz", - "integrity": "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.21.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "vue": ">=3.2" - } - }, - "node_modules/@tanstack/vue-virtual": { - "version": "3.13.10", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.13.10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.0.0" - } - }, - "node_modules/@tsconfig/node22": { - "version": "22.0.5", - "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.5.tgz", - "integrity": "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/svelte": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-1.0.13.tgz", - "integrity": "sha512-5lYJP45Xllo4yE/RUBccBT32eBlRDbqN8r1/MIvQbKxW3aFqaYPCNgm8D5V20X4ShHcwvYWNlKg3liDh1MlBoA==", - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/adm-zip": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz", - "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", - "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", - "license": "MIT" - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "peer": true, + "peer": true, "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", @@ -3832,16 +3182,16 @@ "peer": true }, "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", "license": "MIT", "peer": true }, "node_modules/@types/d3-sankey": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.11.2.tgz", - "integrity": "sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.12.5.tgz", + "integrity": "sha512-/3RZSew0cLAtzGQ+C89hq/Rp3H20QJuVRSqFy6RKLe7E0B8kd2iOS1oBsodrgds4PcNVpqWhdUEng/SHvBcJ6Q==", "license": "MIT", "peer": true, "dependencies": { @@ -3983,8 +3333,10 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/event-source-polyfill": { @@ -3995,6 +3347,8 @@ }, "node_modules/@types/fs-extra": { "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -4008,9 +3362,9 @@ "peer": true }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "dev": true, "license": "MIT", "dependencies": { @@ -4032,6 +3386,8 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/leaflet": { @@ -4046,6 +3402,8 @@ }, "node_modules/@types/linkify-it": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true, "license": "MIT" }, @@ -4070,6 +3428,8 @@ }, "node_modules/@types/markdown-it": { "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", "dependencies": { @@ -4089,20 +3449,24 @@ }, "node_modules/@types/mdurl": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true, "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/parse-json": { @@ -4168,363 +3532,141 @@ "dependencies": { "@types/geojson": "*", "@types/topojson-client": "*", - "@types/topojson-server": "*", - "@types/topojson-simplify": "*", - "@types/topojson-specification": "*" - } - }, - "node_modules/@types/topojson-client": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz", - "integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/geojson": "*", - "@types/topojson-specification": "*" - } - }, - "node_modules/@types/topojson-server": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/topojson-server/-/topojson-server-3.0.4.tgz", - "integrity": "sha512-5+ieK8ePfP+K2VH6Vgs1VCt+fO1U8XZHj0UsF+NktaF0DavAo1q3IvCBXgokk/xmtvoPltSUs6vxuR/zMdOE1g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/geojson": "*", - "@types/topojson-specification": "*" - } - }, - "node_modules/@types/topojson-simplify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/topojson-simplify/-/topojson-simplify-3.0.3.tgz", - "integrity": "sha512-sBO5UZ0O2dB0bNwo0vut2yLHhj3neUGi9uL7/ROdm8Gs6dtt4jcB9OGDKr+M2isZwQM2RuzVmifnMZpxj4IGNw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/geojson": "*", - "@types/topojson-specification": "*" - } - }, - "node_modules/@types/topojson-specification": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz", - "integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "license": "MIT", - "optional": true - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/uuid": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz", - "integrity": "sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==", - "deprecated": "This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.", - "license": "MIT", - "dependencies": { - "uuid": "*" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "license": "MIT" - }, - "node_modules/@types/whatwg-mimetype": { - "version": "3.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", - "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.4", - "@typescript-eslint/type-utils": "8.59.4", - "@typescript-eslint/utils": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.59.4", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", - "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.4", - "@typescript-eslint/types": "^8.59.4", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz", - "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", - "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz", - "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4", - "@typescript-eslint/utils": "8.59.4", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", - "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/topojson-server": "*", + "@types/topojson-simplify": "*", + "@types/topojson-specification": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", - "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", - "dev": true, + "node_modules/@types/topojson-client": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz", + "integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/project-service": "8.59.4", - "@typescript-eslint/tsconfig-utils": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@types/geojson": "*", + "@types/topojson-specification": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", - "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==", - "dev": true, + "node_modules/@types/topojson-server": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/topojson-server/-/topojson-server-3.0.4.tgz", + "integrity": "sha512-5+ieK8ePfP+K2VH6Vgs1VCt+fO1U8XZHj0UsF+NktaF0DavAo1q3IvCBXgokk/xmtvoPltSUs6vxuR/zMdOE1g==", "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@types/geojson": "*", + "@types/topojson-specification": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", - "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", - "dev": true, + "node_modules/@types/topojson-simplify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/topojson-simplify/-/topojson-simplify-3.0.3.tgz", + "integrity": "sha512-sBO5UZ0O2dB0bNwo0vut2yLHhj3neUGi9uL7/ROdm8Gs6dtt4jcB9OGDKr+M2isZwQM2RuzVmifnMZpxj4IGNw==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "8.59.4", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@types/geojson": "*", + "@types/topojson-specification": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, + "node_modules/@types/topojson-specification": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz", + "integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==", "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" + "peer": true, + "dependencies": { + "@types/geojson": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz", + "integrity": "sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==", + "deprecated": "This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "uuid": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "license": "MIT" }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.5" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", - "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -4539,15 +3681,15 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", - "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.64.0", - "@typescript-eslint/types": "^8.64.0", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -4561,15 +3703,15 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", - "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4579,10 +3721,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", - "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -4596,35 +3738,17 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", - "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", - "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.64.0", - "@typescript-eslint/tsconfig-utils": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "engines": { @@ -4635,19 +3759,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", - "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.64.0", - "eslint-visitor-keys": "^5.0.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -4656,67 +3777,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/typescript-estree": { "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", - "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0" + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4724,20 +3800,33 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/utils": { "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { @@ -4772,9 +3861,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "dev": true, "license": "ISC" }, @@ -4800,17 +3889,35 @@ } }, "node_modules/@unovis/ts": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@unovis/ts/-/ts-1.6.2.tgz", - "integrity": "sha512-kSOWRiZsU4Kk1yuJSSaJs+zJCKNg3+2A0/6/7/y1Cu5uHggwGPeE3CzKaNOal/Hpc46bnZw2VLI23mY+gDFbeQ==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@unovis/ts/-/ts-1.6.7.tgz", + "integrity": "sha512-CXmr+EQQyba9vqcyhvc963y06agBvaX0ut+lylTSUo4A/t1YYdrFQFKUULasdVY6Pe+OK5xveZOlJrYDiPOX3A==", "license": "Apache-2.0", "peer": true, "dependencies": { "@emotion/css": "^11.7.1", "@juggle/resize-observer": "^3.3.1", "@types/d3": "^7.4.0", + "@types/d3-array": "~3.2.2", + "@types/d3-axis": "~3.0.6", + "@types/d3-brush": "~3.0.6", + "@types/d3-chord": "~3.0.6", "@types/d3-collection": "^1.0.10", - "@types/d3-sankey": "^0.11.2", + "@types/d3-color": "~3.1.3", + "@types/d3-drag": "~3.0.7", + "@types/d3-ease": "~3.0.2", + "@types/d3-force": "~3.0.10", + "@types/d3-geo": "~3.1.0", + "@types/d3-hierarchy": "~3.1.7", + "@types/d3-interpolate": "~3.0.4", + "@types/d3-path": "~3.1.1", + "@types/d3-sankey": "^0.12.4", + "@types/d3-scale": "~4.0.9", + "@types/d3-selection": "~3.0.0", + "@types/d3-shape": "~3.1.7", + "@types/d3-timer": "~3.0.2", + "@types/d3-transition": "~3.0.9", + "@types/d3-zoom": "~3.0.8", "@types/dagre": "^0.7.50", "@types/geojson": "^7946.0.8", "@types/leaflet": "1.7.6", @@ -4823,10 +3930,28 @@ "@unovis/dagre-layout": "0.8.8-2", "@unovis/graphlibrary": "2.2.0-2", "d3": "^7.2.1", + "d3-array": "~3", + "d3-axis": "~3", + "d3-brush": "~3", + "d3-chord": "~3", "d3-collection": "^1.0.7", + "d3-color": "~3", + "d3-drag": "~3", + "d3-ease": "~3", + "d3-force": "~3", + "d3-geo": "~3", "d3-geo-projection": "^4.0.0", + "d3-hierarchy": "~3", + "d3-interpolate": "~3", "d3-interpolate-path": "^2.2.3", + "d3-path": "~3", "d3-sankey": "^0.12.3", + "d3-scale": "~4", + "d3-selection": "~3", + "d3-shape": "~3", + "d3-timer": "~3", + "d3-transition": "~3", + "d3-zoom": "~3", "elkjs": "^0.10.0", "geojson": "^0.5.0", "leaflet": "1.7.1", @@ -4835,18 +3960,17 @@ "supercluster": "^7.1.5", "three": "^0.135.0", "throttle-debounce": "^5.0.0", - "to-px": "^1.1.0", "topojson-client": "^3.1.0", "tslib": "^2.3.1" } }, "node_modules/@unovis/vue": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@unovis/vue/-/vue-1.6.2.tgz", - "integrity": "sha512-vFglLeiTnNk8VaCEjctAMQXi8Vi+dc217JLkbiIkOLNLXzOClv1ZLv7QjKplRHG+eq/DKC17WSBKINsjXuYbtQ==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@unovis/vue/-/vue-1.6.7.tgz", + "integrity": "sha512-iyYdwwI8Tm8Kyb3aUvG3GVmId6gcmw8EY2Qbaim1W4uqktDYViFzJXIjLPbNir4ju+ziXmaxM2sfhfEC3t3TYw==", "license": "Apache-2.0", "peerDependencies": { - "@unovis/ts": "1.6.2", + "@unovis/ts": "1.6.7", "vue": "^3" } }, @@ -4881,16 +4005,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", - "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4899,13 +4023,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", - "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.7", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4936,9 +4060,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", - "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4949,13 +4073,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", - "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.7", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -4963,14 +4087,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", - "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4979,9 +4103,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", - "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -4989,13 +4113,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", - "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.7", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -5003,6 +4127,13 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/utils/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/@volar/language-core": { "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", @@ -5033,649 +4164,303 @@ } }, "node_modules/@vue-macros/common": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", - "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", "license": "MIT", "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", - "unplugin-utils": "^0.3.0" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/vue-macros" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", - "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", - "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.2", - "@vue/babel-helper-vue-transform-on": "1.5.0", - "@vue/babel-plugin-resolve-type": "1.5.0", - "@vue/shared": "^3.5.18" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - } - } - }, - "node_modules/@vue/babel-plugin-resolve-type": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", - "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/parser": "^7.28.0", - "@vue/compiler-sfc": "^3.5.18" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", - "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.39", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", - "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", - "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.39", - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", - "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.7", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.7" - } - }, - "node_modules/@vue/devtools-core": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.1.5.tgz", - "integrity": "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.1.5", - "@vue/devtools-shared": "^8.1.5" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/@vue/devtools-core/node_modules/@vue/devtools-kit": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", - "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.1.5", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" - } - }, - "node_modules/@vue/devtools-core/node_modules/@vue/devtools-shared": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", - "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/devtools-core/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/eslint-config-prettier": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", - "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.2" - }, - "peerDependencies": { - "eslint": ">= 8.21.0", - "prettier": ">= 3.0.0" - } - }, - "node_modules/@vue/eslint-config-typescript": { - "version": "14.9.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.9.0.tgz", - "integrity": "sha512-E3j9hDlfVf10F30MRcLTPY2IIhWIx1nsvkVukk14kTcuA+oBVot9zsP1hzsO+PAMDxV3Fd9FimBJtUBNBL5KFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^8.60.0", - "fast-glob": "^3.3.3", - "typescript-eslint": "^8.60.0", - "vue-eslint-parser": "^10.4.0" - }, - "bin": { - "vue-eslint-config-typescript": "dist/bin.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^9.10.0 || ^10.0.0", - "eslint-plugin-vue": "^9.28.0 || ^10.0.0", - "typescript": ">=4.8.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", - "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "unplugin-utils": "^0.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=20.19.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/vue-macros" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } } }, - "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } } }, - "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sxzz" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@vue/eslint-config-typescript/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@vue/eslint-config-typescript/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", - "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", - "dev": true, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.65.0", - "@typescript-eslint/parser": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", - "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", - "dev": true, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/type-utils": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.65.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", - "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", - "dev": true, + "node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@vue/devtools-kit": "^8.2.1" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", - "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "node_modules/@vue/devtools-core": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz", + "integrity": "sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "@vue/devtools-kit": "^8.2.1", + "@vue/devtools-shared": "^8.2.1" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "vue": "^3.0.0" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "license": "MIT" + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", + "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" } }, - "node_modules/@vue/eslint-config-typescript/node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "node_modules/@vue/eslint-config-typescript": { + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.9.0.tgz", + "integrity": "sha512-E3j9hDlfVf10F30MRcLTPY2IIhWIx1nsvkVukk14kTcuA+oBVot9zsP1hzsO+PAMDxV3Fd9FimBJtUBNBL5KFA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.60.0", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.60.0", + "vue-eslint-parser": "^10.4.0" + }, + "bin": { + "vue-eslint-config-typescript": "dist/bin.js" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "eslint": "^9.10.0 || ^10.0.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@vue/language-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.6.tgz", - "integrity": "sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.8.tgz", + "integrity": "sha512-ieGT8jJdhhy0mGzStZhsg/qPw5bQZJg5yF+3+XU6saf4sM7yo9ZXy3h+nCwrm2+b4qS/SypkNdR2jAF3uei9tA==", "dev": true, "license": "MIT", "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", - "alien-signals": "^3.0.0", + "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", - "picomatch": "^4.0.2" - } - }, - "node_modules/@vue/language-core/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "picomatch": "^4.0.4" } }, "node_modules/@vue/reactivity": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", - "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.39" + "@vue/shared": "3.5.40" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", - "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", - "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/runtime-core": "3.5.39", - "@vue/shared": "3.5.39", + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", - "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "vue": "3.5.39" + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" } }, "node_modules/@vue/shared": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", - "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", "license": "MIT" }, "node_modules/@vue/test-utils": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", - "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.11.tgz", + "integrity": "sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==", "dev": true, "license": "MIT", "dependencies": { "js-beautify": "^1.14.9", - "vue-component-type-helpers": "^2.0.0" + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } } }, "node_modules/@vue/tsconfig": { @@ -5698,27 +4483,15 @@ } }, "node_modules/@vueuse/core": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", - "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "13.9.0", - "@vueuse/shared": "13.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/core/node_modules/@vueuse/shared": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", - "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" }, @@ -5727,14 +4500,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.2.1.tgz", - "integrity": "sha512-2LIUpBi/67PoXJGqSDQUF0pgQWpNHh7beiA+KG2AbybcNm+pTGWT6oPGlBgUoDWmYwfeQqM/uzOHqcILpKL7nA==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.3.0.tgz", + "integrity": "sha512-76I5FT2ESvCmCaSwapI+a/u/CFtNXmzl9f9lNp1hRtx8vKB8hfiokJr8IvQqcQG5ckGXElyXK516b54ozV3MvA==", "dev": true, "license": "MIT", "dependencies": { - "@vueuse/core": "14.2.1", - "@vueuse/shared": "14.2.1" + "@vueuse/core": "14.3.0", + "@vueuse/shared": "14.3.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -5793,47 +4566,19 @@ } } }, - "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", - "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.1", - "@vueuse/shared": "14.2.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", - "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@vueuse/metadata": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", - "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz", - "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -5860,6 +4605,8 @@ }, "node_modules/abbrev": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, "license": "ISC", "engines": { @@ -5867,9 +4614,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5880,6 +4627,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5909,1800 +4658,2213 @@ } }, "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.4.tgz", + "integrity": "sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/alien-signals": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", - "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "node_modules/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { - "environment": "^1.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "20 || >=22" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "fill-range": "^7.1.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" }, - "engines": { - "node": ">=8" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, "engines": { - "node": ">=14" + "node": ">=4.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/aria-hidden": { - "version": "1.2.6", + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.0.0" + "run-applescript": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/ast-kit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", - "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "pathe": "^2.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" + "node": ">= 0.4" } }, - "node_modules/ast-walker-scope": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", - "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.4", - "ast-kit": "^2.1.3" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=20.19.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sxzz" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, - "node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "peer": true, + "engines": { + "node": ">=6" } }, - "node_modules/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/b4a": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", - "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } + "license": "MIT", + "engines": { + "node": ">=18" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "1.0.2", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/bare-events": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", - "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/bare-fs": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", - "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" + "readdirp": "^5.0.0" }, "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" + "node": ">= 20.19.0" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/bare-os": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", - "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "node_modules/chromium-bidi": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz", + "integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, "engines": { - "bare": ">=1.14.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", "license": "Apache-2.0", "dependencies": { - "bare-os": "^3.0.1" + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" } }, - "node_modules/bare-stream": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", - "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "engines": { + "node": ">=20" } }, - "node_modules/bare-url": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", - "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" - } + "license": "MIT" }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", + "node": ">=18" + }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/braces": { - "version": "3.0.3", - "dev": true, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/browserslist": { - "version": "4.25.0", + "node_modules/codesight": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/codesight/-/codesight-1.18.0.tgz", + "integrity": "sha512-OSR+piddwDipWh7s5ohAt+IjhE9pP5ZfscB4oUTmMGQ1yu1+u0Ouk61R/sc4JAlBjMQzmsG4fHywRtgo+TYlbA==", + "dev": true, "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" + "type": "github", + "url": "https://github.com/sponsors/Houseofmvps" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "individual", + "url": "https://www.kailxlabs.co" } ], "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, "bin": { - "browserslist": "cli.js" + "codesight": "dist/index.js" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=18.0.0" } }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "run-applescript": "^7.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=7.0.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", + "peer": true, "engines": { - "node": ">=6" + "node": ">= 10" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001724", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT", + "peer": true + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/character-entities": { - "version": "2.0.2", + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "peer": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">= 6" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/character-entities-legacy": { + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT", + "peer": true + }, + "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "peer": true, "dependencies": { - "readdirp": "^5.0.0" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" }, "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=12" } }, - "node_modules/chromium-bidi": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz", - "integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "peer": true, "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" + "internmap": "1 - 2" }, "engines": { - "node": ">=20.19.0 <22.0.0 || >=22.12.0" - }, - "peerDependencies": { - "devtools-protocol": "*" + "node": ">=12" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "peer": true, "dependencies": { - "clsx": "^2.1.1" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" }, - "funding": { - "url": "https://polar.sh/cva" + "engines": { + "node": ">=12" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "peer": true, "dependencies": { - "restore-cursor": "^5.0.0" + "d3-path": "1 - 3" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "peer": true, "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" + "d3-array": "^3.2.0" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "license": "ISC", + "peer": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "delaunator": "5" }, "engines": { "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "node_modules/d3-drag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "peer": true, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "peer": true, "dependencies": { - "ansi-regex": "^5.0.1" + "d3-dsv": "1 - 3" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "peer": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=12" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/codesight": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/codesight/-/codesight-1.18.0.tgz", - "integrity": "sha512-OSR+piddwDipWh7s5ohAt+IjhE9pP5ZfscB4oUTmMGQ1yu1+u0Ouk61R/sc4JAlBjMQzmsG4fHywRtgo+TYlbA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/Houseofmvps" - }, - { - "type": "individual", - "url": "https://www.kailxlabs.co" - } - ], - "license": "MIT", - "bin": { - "codesight": "dist/index.js" + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "d3-array": "2.5.0 - 3" }, "engines": { - "node": ">=18.0.0" + "node": ">=12" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "peer": true, "dependencies": { - "delayed-stream": "~1.0.0" + "d3-color": "1 - 3" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node": ">=12" } }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "license": "MIT" + "node_modules/d3-interpolate-path": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", + "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", + "license": "BSD-3-Clause", + "peer": true }, - "node_modules/config-chain": { - "version": "1.1.13", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "3.0.5", - "license": "MIT", - "dependencies": { - "is-what": "^4.1.8" - }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" + "node": ">=12" } }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", "peer": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "license": "ISC", "peer": true, "engines": { - "node": ">= 6" + "node": ">=12" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "peer": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "internmap": "^1.0.0" } }, - "node_modules/csscolorparser": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", - "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", - "license": "MIT", + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause", "peer": true }, - "node_modules/cssesc": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "d3-path": "1" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC", + "peer": true }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", "peer": true, "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { "node": ">=12" } }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", "peer": true, "dependencies": { - "internmap": "1 - 2" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-axis": { + "node_modules/d3-selection": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", "peer": true, "engines": { "node": ">=12" } }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", "peer": true, "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" + "d3-path": "^3.1.0" }, "engines": { "node": ">=12" } }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", "peer": true, "dependencies": { - "d3-path": "1 - 3" + "d3-array": "2 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", "peer": true, "dependencies": { - "d3-array": "^3.2.0" + "d3-time": "1 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", "peer": true, - "dependencies": { - "delaunator": "5" - }, "engines": { "node": ">=12" } }, - "node_modules/d3-dispatch": { + "node_modules/d3-transition": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "license": "ISC", "peer": true, + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, "engines": { "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/d3-drag": { + "node_modules/d3-zoom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", "peer": true, "dependencies": { "d3-dispatch": "1 - 3", - "d3-selection": "3" + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "peer": true, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "peer": true, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "peer": true, "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" + "robust-predicates": "^3.0.2" } }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "peer": true, - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.4.0" } }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "peer": true, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "peer": true, - "dependencies": { - "d3-array": "2.5.0 - 3" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/d3-geo-projection": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", - "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", - "license": "ISC", - "peer": true, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", "dependencies": { - "commander": "7", - "d3-array": "1 - 3", - "d3-geo": "1.12.0 - 3" - }, - "bin": { - "geo2svg": "bin/geo2svg.js", - "geograticule": "bin/geograticule.js", - "geoproject": "bin/geoproject.js", - "geoquantize": "bin/geoquantize.js", - "geostitch": "bin/geostitch.js" + "dequal": "^2.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/d3-geo-projection/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "peer": true, + "node_modules/devtools-protocol": { + "version": "0.0.1638949", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1638949.tgz", + "integrity": "sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "license": "BSD-3-Clause", "engines": { - "node": ">= 10" + "node": ">=0.3.1" } }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "peer": true, + "node_modules/dompurify": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "peer": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-interpolate-path": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", - "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", - "license": "BSD-3-Clause", + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC", "peer": true }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "peer": true, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" + "node_modules/editorconfig/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "peer": true, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "peer": true, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "internmap": "^1.0.0" - } + "node_modules/electron-to-chromium": { + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", + "license": "ISC" }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause", + "node_modules/elkjs": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.10.2.tgz", + "integrity": "sha512-Yx3ORtbAFrXelYkAy2g0eYyVY8QG0XEmGdQXmy0eithKKjbWRfl3Xe884lfkszfBF6UKyIy4LwfcZ3AZc8oxFw==", + "license": "EPL-2.0", "peer": true }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "d3-path": "1" - } + "node_modules/emoji-picker-element": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/emoji-picker-element/-/emoji-picker-element-1.12.1.tgz", + "integrity": "sha512-F9AY/re8uqZmBcCXLHLGvyy7fxuMQdZl9R8OToLRH8Vnns+WMX8RYUbI2nSJklzl5+82qzpYWeus1/puDepWcQ==", + "license": "Apache-2.0" }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC", - "peer": true + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "peer": true, + "node_modules/enhanced-resolve": { + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "peer": true, "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "peer": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "peer": true, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", "dependencies": { - "d3-path": "^3.1.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "peer": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "peer": true, - "dependencies": { - "d3-time": "1 - 3" + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "peer": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "peer": true, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" }, "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "peer": true, - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "jiti": "*" }, - "engines": { - "node": ">=12" + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/date-fns-tz": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", - "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "node_modules/eslint-plugin-prettier": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", + "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, "peerDependencies": { - "date-fns": "^3.0.0 || ^4.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/dayjs": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", - "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/eslint-plugin-vue": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.10.0.tgz", + "integrity": "sha512-dL9x9rBHqqNcByWiLOHK6L0SB97V82/NC0cZRn9cXPjM7pCuWlpQQP9bFH4vjBv80ej1ZpzAkuD8zWH1o9bZbA==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@eslint-community/eslint-utils": "^4.9.1", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^7.1.4", + "semver": "^7.8.5", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "vue-eslint-parser": "^10.3.0" }, "peerDependenciesMeta": { - "supports-color": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { "optional": true } } }, - "node_modules/decode-named-character-reference": { - "version": "1.2.0", + "node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, "license": "MIT", "dependencies": { - "character-entities": "^2.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=4" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defu": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz", - "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==", - "license": "MIT" - }, - "node_modules/delaunator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", - "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "robust-predicates": "^3.0.2" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=0.4.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dequal": { - "version": "2.0.3", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 4" } }, - "node_modules/detect-libc": { - "version": "2.0.4", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "dequal": "^2.0.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1608973", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", - "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "BSD-2-Clause", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://opencollective.com/eslint" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "license": "MIT", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=0.10" } }, - "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "license": "ISC", - "peer": true - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/editorconfig": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", - "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "^9.0.1", - "semver": "^7.5.3" - }, - "bin": { - "editorconfig": "bin/editorconfig" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=14" + "node": ">=4.0" } }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=14" + "node": ">=4.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.171", - "license": "ISC" + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, - "node_modules/elkjs": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.10.2.tgz", - "integrity": "sha512-Yx3ORtbAFrXelYkAy2g0eYyVY8QG0XEmGdQXmy0eithKKjbWRfl3Xe884lfkszfBF6UKyIy4LwfcZ3AZc8oxFw==", - "license": "EPL-2.0", - "peer": true + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/emoji-picker-element": { - "version": "1.12.1", - "license": "Apache-2.0" + "node_modules/eval-estree-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eval-estree-expression/-/eval-estree-expression-3.0.1.tgz", + "integrity": "sha512-zTLKGbiVdQYp4rQkSoXPibrFf5ZoPn6jzExegRLEQ13F+FSxu5iLgaRH6hlDs2kWSUa6vp8yD20cdJi0me6pEw==", + "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, + "node_modules/event-source-polyfill": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz", + "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" }, "engines": { - "node": ">=10.13.0" + "node": ">=8.6.0" } }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "engines": { + "node": ">= 6" } }, - "node_modules/env-paths": { - "version": "2.2.1", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/error-ex": { - "version": "1.3.2", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } + "node_modules/fast-uri": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", - "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/es-errors": { - "version": "1.3.0", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, + "node_modules/fflate": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.9.tgz", + "integrity": "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==", "license": "MIT" }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "dependencies": { + "flat-cache": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=16.0.0" } }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "devOptional": true, - "hasInstallScript": true, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "node": ">=8" } }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT", - "engines": { - "node": ">=6" - } + "peer": true }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { "node": ">=10" }, @@ -7710,627 +6872,606 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", - "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", - "workspaces": [ - "packages/*" - ], "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">=16" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.5", + "node_modules/flatted": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } + "license": "ISC" }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.0", + "node_modules/focus-trap": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.2.2.tgz", + "integrity": "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" - }, + "tabbable": "^6.5.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" + "node": ">=4.0" }, "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { + "debug": { "optional": true } } }, - "node_modules/eslint-plugin-vue": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.8.0.tgz", - "integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^7.1.0", - "semver": "^7.6.3", - "xml-name-validator": "^4.0.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "vue-eslint-parser": "^10.0.0" + "node": ">=14" }, - "peerDependenciesMeta": { - "@stylistic/eslint-plugin": { - "optional": true - }, - "@typescript-eslint/parser": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=12" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "license": "Apache-2.0", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", "engines": { - "node": "18 || 20 || >=22" + "node": ">=6.9.0" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, + "node_modules/geojson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", + "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, + "peer": true, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.10" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", + "license": "ISC", + "peer": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.5" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "peer": true, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT", + "peer": true + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "estraverse": "^5.1.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=0.10" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/esrecurse": { - "version": "4.3.0", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "estraverse": "^5.2.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node": ">=10.13.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eval-estree-expression": { - "version": "2.1.1", - "license": "MIT" - }, - "node_modules/event-source-polyfill": { - "version": "1.0.31", - "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz", - "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==", - "license": "MIT" - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "license": "Apache-2.0" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=8.6.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "license": "MIT", + "peer": true, "dependencies": { - "is-glob": "^4.0.1" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" }, "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "dev": true, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "license": "ISC", + "peer": true, "dependencies": { - "reusify": "^1.0.4" + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "node_modules/fflate": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.9.tgz", - "integrity": "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==", - "license": "MIT" + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=16.0.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/fill-range": { - "version": "7.1.1", + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/happy-dom": { + "version": "20.11.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.1.tgz", + "integrity": "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==", "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" }, "engines": { - "node": ">=8" + "node": ">=20.0.0" } }, - "node_modules/find-root": { + "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=16" + "node": ">= 0.4" } }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/focus-trap": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.0.1.tgz", - "integrity": "sha512-9ptSG6z51YQOstI/oN4XuVGP/03u2nh0g//qz7L6zX0i6PZiPnkcf3GenXq7N2hZnASXaMxTPpbKwdI+PFvxlw==", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "dev": true, "license": "MIT", "dependencies": { - "tabbable": "^6.4.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=4.0" + "dependencies": { + "@types/hast": "^3.0.0" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/foreground-child": { - "version": "3.3.1", + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "agent-base": "6", + "debug": "4" }, "engines": { "node": ">= 6" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=0.10.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "peer": true }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">= 4" } }, - "node_modules/geojson": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", - "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": ">= 0.10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/geojson-vt": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", - "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", - "license": "ISC", - "peer": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", + "peer": true, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=12" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "peer": true }, - "node_modules/get-intrinsic": { - "version": "1.3.0", + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", + "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -8339,1211 +7480,1798 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "devOptional": true, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/gl-matrix": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", - "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=8" + } }, - "node_modules/glob": { - "version": "10.5.0", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "is-extglob": "^2.1.1" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-parent": { - "version": "6.0.2", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, "engines": { - "node": ">=6" + "node": ">=0.12.0" } }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/gopd": { - "version": "1.2.0", + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", - "license": "MIT", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=0.4.7" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { - "uglify-js": "^3.1.4" + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/happy-dom": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.9.tgz", - "integrity": "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA==", + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": ">=20.0.0", - "@types/whatwg-mimetype": "^3.0.2", - "@types/ws": "^8.18.1", - "entities": "^7.0.1", - "whatwg-mimetype": "^3.0.0", - "ws": "^8.18.3" + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=20.0.0" + "node": ">=14" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "license": "MIT" }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "universalify": "^2.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/hookable": { - "version": "5.5.3", - "license": "MIT" - }, - "node_modules/html-void-elements": { + "node_modules/kdbush": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==", + "license": "ISC", + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "json-buffer": "3.0.1" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, + "peer": true, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "license": "MIT", - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/leaflet": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", + "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", + "license": "BSD-2-Clause", "peer": true }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { - "node": ">= 4" + "node": ">= 0.8.0" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=6" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.8.19" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/ini": { - "version": "1.3.8", - "license": "ISC" + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "peer": true, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "peer": true, - "dependencies": { - "hasown": "^2.0.2" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.16" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.12.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-what": { - "version": "4.1.16", - "license": "MIT", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=12.13" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/mesqueeb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, "engines": { - "node": ">=16" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT", + "peer": true }, - "node_modules/jackspeak": { - "version": "3.4.3", + "node_modules/lint-staged": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.2.0.tgz", + "integrity": "sha512-FchGnFe4i4B1C/a35SPU9bNGPEHSC1+1iV0plLjzBmKVe9klZrlRfSgK6Cw4VeHyqOXbJUXP0vON61uRftNQ0A==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "picomatch": "^4.0.5", + "string-argv": "^0.3.2", + "tinyexec": "^1.2.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=22.22.1" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/lint-staged" }, "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "yaml": "^2.9.0" } }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/js-beautify": { - "version": "1.15.4", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.4.2", - "js-cookie": "^3.0.5", - "nopt": "^7.2.1" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-cookie": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", - "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, - "node_modules/js-tokens": { - "version": "4.0.0", + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "yallist": "^3.0.2" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" + "node_modules/lucide-vue-next": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-1.0.0.tgz", + "integrity": "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==", + "deprecated": "Package deprecated. Please use @lucide/vue instead.", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } }, - "node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "dev": true, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "node_modules/maplibre-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", + "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.10", + "@types/mapbox__point-geometry": "^0.1.2", + "@types/mapbox__vector-tile": "^1.3.0", + "@types/pbf": "^3.0.2", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.2", + "quickselect": "^2.0.0", + "supercluster": "^7.1.5", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/jsonfile": { - "version": "6.1.0", + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/kdbush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", - "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==", - "license": "ISC", - "peer": true + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } }, - "node_modules/keyv": { - "version": "4.5.4", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" } }, - "node_modules/kleur": { - "version": "4.1.5", + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/leaflet": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", - "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", - "license": "BSD-2-Clause", - "peer": true + "node_modules/micromark-core-commonmark/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "MIT" }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "MIT" }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "license": "MIT", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "license": "MIT" }, - "node_modules/lines-and-columns": { - "version": "1.2.4", + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/lint-staged": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.3.tgz", - "integrity": "sha512-wnvMRhzC3GNpjixxleiG+pAW09dHTUgBCjMS7XouAg5E7wKUc8YdfogpF7zIgvXKDbH+452O6+XpnKm6V67rPw==", - "dev": true, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", "license": "MIT", "dependencies": { - "listr2": "^10.2.1", - "picomatch": "^4.0.4", - "string-argv": "^0.3.2", - "tinyexec": "^1.1.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=22.22.1" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { - "url": "https://opencollective.com/lint-staged" - }, - "optionalDependencies": { - "yaml": "^2.8.4" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lint-staged/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/listr2": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", - "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", - "dev": true, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", "license": "MIT", "dependencies": { - "cli-truncate": "^5.2.0", - "eventemitter3": "^5.0.4", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^10.0.0" + "micromark-util-types": "^1.0.0" }, - "engines": { - "node": ">=22.13.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "node_modules/micromark-extension-gfm-tagfilter/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", "license": "MIT", "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", + "node_modules/micromark-extension-gfm/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", + "node_modules/micromark-factory-space/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "yallist": "^3.0.2" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/lru-cache/node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" + "node_modules/micromark-factory-space/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/lucide-vue-next": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-1.0.0.tgz", - "integrity": "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==", - "deprecated": "Package deprecated. Please use @lucide/vue instead.", - "license": "ISC", - "peerDependencies": { - "vue": ">=3.0.1" + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/magic-string": { - "version": "0.30.21", + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/magic-string-ast": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", - "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "magic-string": "^0.30.19" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/maplibre-gl": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", - "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "@mapbox/geojson-rewind": "^0.5.2", - "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/mapbox-gl-supported": "^2.0.1", - "@mapbox/point-geometry": "^0.1.0", - "@mapbox/tiny-sdf": "^2.0.5", - "@mapbox/unitbezier": "^0.0.1", - "@mapbox/vector-tile": "^1.3.1", - "@mapbox/whoots-js": "^3.1.0", - "@types/geojson": "^7946.0.10", - "@types/mapbox__point-geometry": "^0.1.2", - "@types/mapbox__vector-tile": "^1.3.0", - "@types/pbf": "^3.0.2", - "csscolorparser": "~1.0.3", - "earcut": "^2.2.4", - "geojson-vt": "^3.2.1", - "gl-matrix": "^3.4.3", - "global-prefix": "^3.0.0", - "murmurhash-js": "^1.0.0", - "pbf": "^3.2.1", - "potpack": "^1.0.2", - "quickselect": "^2.0.0", - "supercluster": "^7.1.5", - "tinyqueue": "^2.0.3", - "vt-pbf": "^3.1.3" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/mark.js": { - "version": "8.11.1", - "dev": true, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/math-intrinsics": { + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-types": { "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/mdast-util-to-hast/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", "funding": [ { "type": "GitHub Sponsors", @@ -9556,15 +9284,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/mdast-util-to-hast/node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -9577,11 +9303,10 @@ ], "license": "MIT" }, - "node_modules/mdast-util-to-hast/node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", "funding": [ { "type": "GitHub Sponsors", @@ -9594,16 +9319,15 @@ ], "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/mdast-util-to-hast/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "funding": [ { "type": "GitHub Sponsors", @@ -9614,13 +9338,16 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } }, - "node_modules/mdast-util-to-hast/node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -9633,23 +9360,10 @@ ], "license": "MIT" }, - "node_modules/memorystream": { - "version": "0.3.1", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "3.2.0", + "node_modules/micromark-util-classify-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -9660,29 +9374,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } + "license": "MIT" }, - "node_modules/micromark-core-commonmark": { + "node_modules/micromark-util-combine-extensions": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", "funding": [ { "type": "GitHub Sponsors", @@ -9694,134 +9391,15 @@ } ], "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "2.0.3", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^1.0.0", - "micromark-extension-gfm-footnote": "^1.0.0", - "micromark-extension-gfm-strikethrough": "^1.0.0", - "micromark-extension-gfm-table": "^1.0.0", - "micromark-extension-gfm-tagfilter": "^1.0.0", - "micromark-extension-gfm-task-list-item": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "1.0.5", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "micromark-core-commonmark": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "1.0.7", - "license": "MIT", "dependencies": { "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "1.0.7", - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { "micromark-util-types": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "1.0.5", - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-destination": { + "node_modules/micromark-util-combine-extensions/node_modules/micromark-util-types": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -9832,15 +9410,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-factory-label": { + "node_modules/micromark-util-decode-numeric-character-reference": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", "funding": [ { "type": "GitHub Sponsors", @@ -9853,14 +9428,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-factory-space": { + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -9871,14 +9445,45 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } + "license": "MIT" + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/micromark-factory-title": { + "node_modules/micromark-util-normalize-identifier": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9891,14 +9496,13 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-factory-whitespace": { + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -9909,16 +9513,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-character": { - "version": "1.2.0", + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", "funding": [ { "type": "GitHub Sponsors", @@ -9931,12 +9531,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, - "node_modules/micromark-util-chunked": { + "node_modules/micromark-util-resolve-all/node_modules/micromark-util-types": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -9947,13 +9548,13 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-classify-character": { - "version": "1.1.0", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -9966,13 +9567,15 @@ ], "license": "MIT", "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-combine-extensions": { + "node_modules/micromark-util-subtokenize": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", "funding": [ { "type": "GitHub Sponsors", @@ -9986,11 +9589,15 @@ "license": "MIT", "dependencies": { "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/micromark-util-decode-numeric-character-reference": { + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -10001,13 +9608,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-encode": { + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-types": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -10020,8 +9626,11 @@ ], "license": "MIT" }, - "node_modules/micromark-util-html-tag-name": { - "version": "1.2.0", + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -10034,8 +9643,11 @@ ], "license": "MIT" }, - "node_modules/micromark-util-normalize-identifier": { - "version": "1.1.0", + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, "funding": [ { "type": "GitHub Sponsors", @@ -10046,13 +9658,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-resolve-all": { - "version": "1.1.0", + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "funding": [ { "type": "GitHub Sponsors", @@ -10065,11 +9676,14 @@ ], "license": "MIT", "dependencies": { + "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "1.2.0", + "node_modules/micromark/node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", "funding": [ { "type": "GitHub Sponsors", @@ -10080,15 +9694,12 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } + "license": "MIT" }, - "node_modules/micromark-util-subtokenize": { - "version": "1.1.0", + "node_modules/micromark/node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", "funding": [ { "type": "GitHub Sponsors", @@ -10101,14 +9712,15 @@ ], "license": "MIT", "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-util-symbol": { + "node_modules/micromark/node_modules/micromark-util-symbol": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -10121,8 +9733,10 @@ ], "license": "MIT" }, - "node_modules/micromark-util-types": { + "node_modules/micromark/node_modules/micromark-util-types": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -10137,6 +9751,8 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -10147,6 +9763,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -10168,19 +9797,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -10191,16 +9807,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10208,32 +9824,40 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/minisearch": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", "dev": true, "license": "MIT" }, "node_modules/mitt": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, "node_modules/mlly": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", - "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "license": "MIT", "dependencies": { "acorn": "^8.16.0", @@ -10259,8 +9883,20 @@ "pathe": "^2.0.1" } }, + "node_modules/modern-tar": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz", + "integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "license": "MIT", "engines": { "node": ">=4" @@ -10278,10 +9914,14 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/muggle-string": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "license": "MIT" }, "node_modules/murmurhash-js": { @@ -10292,9 +9932,9 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -10311,19 +9951,30 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/neo-async": { "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "license": "MIT" + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/nopt": { "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "license": "ISC", "dependencies": { @@ -10336,8 +9987,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, "node_modules/npm-normalize-package-bin": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, "license": "ISC", "engines": { @@ -10371,40 +10030,14 @@ "npm": ">= 10" } }, - "node_modules/npm-run-all2/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm-run-all2/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/npm-run-all2/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=18" } }, "node_modules/npm-run-all2/node_modules/which": { @@ -10425,6 +10058,8 @@ }, "node_modules/nth-check": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10436,6 +10071,8 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10445,77 +10082,60 @@ } }, "node_modules/obug": { - "version": "2.1.1", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } }, "node_modules/ohash": { "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "license": "MIT" }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/oniguruma-parser": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", - "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", "dev": true, "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz", - "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", "dev": true, "license": "MIT", "dependencies": { - "oniguruma-parser": "^0.12.1", + "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10523,6 +10143,8 @@ }, "node_modules/openapi-types": { "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "license": "MIT" }, "node_modules/openapi-zod-client": { @@ -10585,42 +10207,6 @@ "openapi-types": ">=7" } }, - "node_modules/openapi-zod-client/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/openapi-zod-client/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/openapi-zod-client/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/openapi-zod-client/node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -10638,6 +10224,8 @@ }, "node_modules/openapi3-ts": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-3.1.0.tgz", + "integrity": "sha512-1qKTvCCVoV0rkwUh1zq5o8QyghmwYPuhdvtjv1rFjuOnJToXhQyF8eGjNETQ8QmGjr9Jz/tkAKLITIl2s7dw3A==", "license": "MIT", "dependencies": { "yaml": "^2.1.3" @@ -10645,6 +10233,8 @@ }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -10661,6 +10251,8 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10675,6 +10267,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -10689,16 +10283,23 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parchment": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", "license": "BSD-3-Clause" }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -10708,7 +10309,10 @@ }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -10722,19 +10326,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "license": "MIT" - }, - "node_modules/parse-unit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-unit/-/parse-unit-1.0.1.tgz", - "integrity": "sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==", - "license": "MIT", - "peer": true - }, "node_modules/pastable": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pastable/-/pastable-2.2.1.tgz", + "integrity": "sha512-K4ClMxRKpgN4sXj6VIPPrvor/TMp2yPNCGtfhvV106C73SwefQ3FuegURsH7AQHpqu0WwbvKXRl1HQxF6qax9w==", "dependencies": { "@babel/core": "^7.20.12", "ts-toolbelt": "^9.6.0", @@ -10758,6 +10353,8 @@ }, "node_modules/pastable/node_modules/type-fest": { "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=14.16" @@ -10775,6 +10372,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -10783,6 +10382,8 @@ }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -10798,6 +10399,8 @@ }, "node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10813,6 +10416,8 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, @@ -10828,6 +10433,8 @@ }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, "node_modules/pbf": { @@ -10845,34 +10452,39 @@ } }, "node_modules/pdf-vue3": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/pdf-vue3/-/pdf-vue3-1.0.12.tgz", - "integrity": "sha512-7SMTx1RfRwdc+2WPniDzqM8MxJLqTNNzdyV0SeQTxeRLJGndb5Wv/fz5afO13oBSIvvaqcbZ/S3gF+XjqkSb9g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pdf-vue3/-/pdf-vue3-2.0.1.tgz", + "integrity": "sha512-1hH48DEmJhSsHo27IFVp0aj97gkI1oe1Fvk6Xz1i8hNNkT6e2QrZ9fCmiVsL+I6bDCWL/e3LvskRTPRlflH5tw==", "license": "MIT" }, "node_modules/perfect-debounce": { - "version": "1.0.0", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pidtree": { - "version": "0.6.0", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", "dev": true, "license": "MIT", "bin": { @@ -10883,73 +10495,77 @@ } }, "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.2.tgz", + "integrity": "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g==", "license": "MIT", "dependencies": { - "@vue/devtools-api": "^7.7.7" + "nostics": "^1.1.4" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "typescript": ">=4.5.0", + "@vue/devtools-api": "^8.1.5", + "typescript": ">=5.6.0", "vue": "^3.5.11" }, "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": false + }, "typescript": { "optional": true } } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", "license": "MIT", "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", + "confbox": "^0.2.4", + "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "node_modules/playwright": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", - "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz", + "integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.60.0" + "playwright-core": "1.62.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.0.tgz", + "integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -10966,7 +10582,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10975,10 +10591,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -10995,8 +10610,23 @@ "license": "ISC", "peer": true }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -11004,9 +10634,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { @@ -11020,7 +10650,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -11030,20 +10662,10 @@ "node": ">=6.0.0" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "dev": true, "license": "MIT", "funding": { @@ -11053,6 +10675,8 @@ }, "node_modules/proto-list": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true, "license": "ISC" }, @@ -11063,15 +10687,13 @@ "license": "MIT", "peer": true }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "engines": { + "node": ">=10" } }, "node_modules/punycode": { @@ -11085,18 +10707,18 @@ } }, "node_modules/puppeteer": { - "version": "25.0.2", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.0.2.tgz", - "integrity": "sha512-cXj/5RlDCzSC7k1YdBIm6prb8lK8lEdmScVbcalX1rBn4fqNN1UNuEz/HZZYiDLsK8dOGvyLpGjh6CgxCyqKtg==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.3.0.tgz", + "integrity": "sha512-O1tx8S315aw8eI99HZ5ZNcVEzJ9+jKF//eO5UvfZ3cXJ6okZ5sX3Y50u7DJaM+ewEK4LqXP068tBhfRaWikj+g==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "3.0.2", + "@puppeteer/browsers": "3.0.6", "chromium-bidi": "16.0.1", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1608973", - "puppeteer-core": "25.0.2", + "devtools-protocol": "0.0.1638949", + "lilconfig": "^3.1.3", + "puppeteer-core": "25.3.0", "typed-query-selector": "^2.12.2" }, "bin": { @@ -11107,58 +10729,31 @@ } }, "node_modules/puppeteer-core": { - "version": "25.0.2", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.0.2.tgz", - "integrity": "sha512-Q0IUIHER1S9PiNIfdNFc+pVOj79Tp4b9v0Fv4enigwsLy0Hbgq45KFgqzmN31DeCXh+Uvxnt9r7fMERhAMjs8Q==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.3.0.tgz", + "integrity": "sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "3.0.2", + "@puppeteer/browsers": "3.0.6", "chromium-bidi": "16.0.1", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1608973", + "devtools-protocol": "0.0.1638949", "typed-query-selector": "^2.12.2", - "webdriver-bidi-protocol": "0.4.1", - "ws": "^8.20.0" + "webdriver-bidi-protocol": "0.4.2", + "ws": "^8.21.0" }, "engines": { "node": ">=22.12.0" } }, - "node_modules/puppeteer/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -11185,6 +10780,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -11226,6 +10823,8 @@ }, "node_modules/quill-delta": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", "license": "MIT", "dependencies": { "fast-diff": "^1.3.0", @@ -11244,6 +10843,8 @@ }, "node_modules/read-package-json-fast": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", "dev": true, "license": "ISC", "dependencies": { @@ -11254,6 +10855,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", @@ -11295,9 +10906,9 @@ "license": "MIT" }, "node_modules/reka-ui": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.8.2.tgz", - "integrity": "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.1.tgz", + "integrity": "sha512-drcOQ4rQtDYAcGCsyQBqQg8QQ+H3B+zDaMJU0h8KPEPMa7g9BHu3zcOi4OB39XJSWizceFoNO0Z9tctSGLOXqg==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -11308,7 +10919,7 @@ "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", - "defu": "^6.1.4", + "defu": "^6.1.5", "ohash": "^2.0.11" }, "funding": { @@ -11316,47 +10927,13 @@ "url": "https://github.com/sponsors/zernonia" }, "peerDependencies": { - "vue": ">= 3.2.0" - } - }, - "node_modules/reka-ui/node_modules/@vueuse/core": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", - "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.1", - "@vueuse/shared": "14.2.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/reka-ui/node_modules/@vueuse/metadata": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", - "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "vue": ">= 3.4.0" } }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11386,19 +10963,14 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/resolve-protobuf-schema": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", @@ -11409,25 +10981,10 @@ "protocol-buffers-schema": "^3.3.1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/reusify": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -11437,6 +10994,8 @@ }, "node_modules/rfdc": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, "node_modules/robust-predicates": { @@ -11447,12 +11006,12 @@ "peer": true }, "node_modules/rolldown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", - "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.138.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -11462,31 +11021,31 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.4", - "@rolldown/binding-darwin-arm64": "1.1.4", - "@rolldown/binding-darwin-x64": "1.1.4", - "@rolldown/binding-freebsd-x64": "1.1.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", - "@rolldown/binding-linux-arm64-gnu": "1.1.4", - "@rolldown/binding-linux-arm64-musl": "1.1.4", - "@rolldown/binding-linux-ppc64-gnu": "1.1.4", - "@rolldown/binding-linux-s390x-gnu": "1.1.4", - "@rolldown/binding-linux-x64-gnu": "1.1.4", - "@rolldown/binding-linux-x64-musl": "1.1.4", - "@rolldown/binding-openharmony-arm64": "1.1.4", - "@rolldown/binding-wasm32-wasi": "1.1.4", - "@rolldown/binding-win32-arm64-msvc": "1.1.4", - "@rolldown/binding-win32-x64-msvc": "1.1.4" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", - "dev": true, + "version": "4.62.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.3.tgz", + "integrity": "sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==", + "devOptional": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -11496,74 +11055,74 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", + "@rollup/rollup-android-arm-eabi": "4.62.3", + "@rollup/rollup-android-arm64": "4.62.3", + "@rollup/rollup-darwin-arm64": "4.62.3", + "@rollup/rollup-darwin-x64": "4.62.3", + "@rollup/rollup-freebsd-arm64": "4.62.3", + "@rollup/rollup-freebsd-x64": "4.62.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.3", + "@rollup/rollup-linux-arm-musleabihf": "4.62.3", + "@rollup/rollup-linux-arm64-gnu": "4.62.3", + "@rollup/rollup-linux-arm64-musl": "4.62.3", + "@rollup/rollup-linux-loong64-gnu": "4.62.3", + "@rollup/rollup-linux-loong64-musl": "4.62.3", + "@rollup/rollup-linux-ppc64-gnu": "4.62.3", + "@rollup/rollup-linux-ppc64-musl": "4.62.3", + "@rollup/rollup-linux-riscv64-gnu": "4.62.3", + "@rollup/rollup-linux-riscv64-musl": "4.62.3", + "@rollup/rollup-linux-s390x-gnu": "4.62.3", + "@rollup/rollup-linux-x64-gnu": "4.62.3", + "@rollup/rollup-linux-x64-musl": "4.62.3", + "@rollup/rollup-openbsd-x64": "4.62.3", + "@rollup/rollup-openharmony-arm64": "4.62.3", + "@rollup/rollup-win32-arm64-msvc": "4.62.3", + "@rollup/rollup-win32-ia32-msvc": "4.62.3", + "@rollup/rollup-win32-x64-gnu": "4.62.3", + "@rollup/rollup-win32-x64-msvc": "4.62.3", "fsevents": "~2.3.2" } }, "node_modules/rrdom": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.1.0.tgz", - "integrity": "sha512-d7oaIHzwffxI74UJMZDGq/f97/Yfm3QX4CcTIlQelt2HsLavItLjp8x8nQ0g9Pet5+5wG1RGr9yvsxb1gJlo8A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.1.1.tgz", + "integrity": "sha512-VBkTF3bGNqcZjqnbo/gKi9GVQPIxiG0dofw7CQdAZQFQ2juJdt2YKIf3oS9QudL8HTpGh9bz5TUN+3amBn1Geg==", "license": "MIT", "dependencies": { - "rrweb-snapshot": "^2.1.0" + "rrweb-snapshot": "^2.1.1" } }, "node_modules/rrweb": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.1.0.tgz", - "integrity": "sha512-gGGvd1eXWnOuJpQumH7+gPmBwTXzNrjmQyedWVePcEx0S5fe3VkQW4vw5f5ItY+vuigaaktte9cZKOg0OEvv+w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.1.1.tgz", + "integrity": "sha512-ToxhJg3SsrAhw+/DPhI/2iiwZQIrGK5BGkZ0kHn4qUExcvQYRaolkciD1FWX2+r6vf1IxFyhsoRY18h3D+XoCg==", "license": "MIT", "dependencies": { - "@rrweb/types": "^2.1.0", - "@rrweb/utils": "^2.1.0", + "@rrweb/types": "^2.1.1", + "@rrweb/utils": "^2.1.1", "@types/css-font-loading-module": "0.0.7", "@xstate/fsm": "^1.4.0", "base64-arraybuffer": "^1.0.1", "mitt": "^3.0.0", - "rrdom": "^2.1.0", - "rrweb-snapshot": "^2.1.0" + "rrdom": "^2.1.1", + "rrweb-snapshot": "^2.1.1" } }, "node_modules/rrweb-player": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rrweb-player/-/rrweb-player-2.1.0.tgz", - "integrity": "sha512-EU0dCgrN66o2E8GAT67wDBRMgcJ1QJuu2lGa8PKO3xSosSS2eCaHlJWmX9fFCMvKkLsahWe15GnuqUjprCGMoA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rrweb-player/-/rrweb-player-2.1.1.tgz", + "integrity": "sha512-oiAWx/m9Pz0DD5jlJdrF8QlotOZZBK0jINNSYAixxhGa2dI1TrzVVmF4eWFSd6AR8f2zjazKXI/PXCqj3PttXQ==", "license": "MIT", "dependencies": { - "@rrweb/packer": "^2.1.0", - "@rrweb/replay": "^2.1.0", + "@rrweb/packer": "^2.1.1", + "@rrweb/replay": "^2.1.1", "@tsconfig/svelte": "^1.0.0" } }, "node_modules/rrweb-snapshot": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.1.0.tgz", - "integrity": "sha512-CFjUKWlO+Dl72gk8qwcDcNE/Pj9yr6IvGRAiRAx12pmJaSWaOKFoFgpQQ1j/mW0WKwEZXbHnMJL8M92gIsdwUQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.1.1.tgz", + "integrity": "sha512-al4Kx7Am7YlIn/5Yb2EMr7cdmjGiK+cLhdilJEXqkFXgpnys8t/yMJumXy2Ormgirpg3MBnFha0GTgny+iIL2w==", "license": "MIT", "dependencies": { "postcss": "^8.4.38" @@ -11584,6 +11143,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -11613,6 +11174,8 @@ }, "node_modules/sade": { "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "license": "MIT", "dependencies": { "mri": "^1.1.0" @@ -11635,9 +11198,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -11649,6 +11212,8 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -11660,6 +11225,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -11667,9 +11234,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { @@ -11697,12 +11264,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -11714,11 +11283,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -11729,6 +11300,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11745,6 +11318,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11762,11 +11337,15 @@ }, "node_modules/siginfo": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/signal-exit": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -11791,51 +11370,26 @@ "node": ">=18" } }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/sortablejs": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", - "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", + "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", "license": "MIT" }, "node_modules/source-map": { - "version": "0.6.1", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11854,6 +11408,8 @@ }, "node_modules/speakingurl": { "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11861,30 +11417,22 @@ }, "node_modules/stackback": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, - "node_modules/streamx": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", - "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string-argv": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", "engines": { @@ -11892,17 +11440,18 @@ } }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=20" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11911,6 +11460,8 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -11924,6 +11475,8 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -11932,19 +11485,15 @@ }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -11988,6 +11537,8 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -11999,6 +11550,8 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -12030,10 +11583,12 @@ } }, "node_modules/superjson": { - "version": "2.2.2", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", "license": "MIT", "dependencies": { - "copy-anything": "^3.0.2" + "copy-anything": "^4" }, "engines": { "node": ">=16" @@ -12053,11 +11608,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.3.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -12067,9 +11624,9 @@ } }, "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", "dev": true, "license": "MIT" }, @@ -12084,13 +11641,15 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tanu": { "version": "0.1.13", + "resolved": "https://registry.npmjs.org/tanu/-/tanu-0.1.13.tgz", + "integrity": "sha512-UbRmX7ccZ4wMVOY/Uw+7ji4VOkEYSYJG1+I4qzbnn4qh/jtvVbrm6BFnF12NQQ4+jGv21wKmjb1iFyUSVnBWcQ==", "license": "MIT", "dependencies": { "tslib": "^2.4.0", @@ -12099,6 +11658,8 @@ }, "node_modules/tanu/node_modules/typescript": { "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12121,54 +11682,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", - "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/text-decoder": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", - "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, "node_modules/three": { "version": "0.135.0", "resolved": "https://registry.npmjs.org/three/-/three-0.135.0.tgz", @@ -12188,13 +11701,15 @@ }, "node_modules/tinybench": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -12217,33 +11732,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyqueue": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", @@ -12257,22 +11745,14 @@ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-px": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/to-px/-/to-px-1.1.0.tgz", - "integrity": "sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw==", - "license": "MIT", - "peer": true, - "dependencies": { - "parse-unit": "^1.0.1" + "engines": { + "node": ">=14.0.0" } }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12339,26 +11819,31 @@ } }, "node_modules/ts-pattern": { - "version": "5.7.1", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.9.0.tgz", + "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", "license": "MIT" }, "node_modules/ts-toolbelt": { "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", "license": "Apache-2.0" }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "devOptional": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -12395,6 +11880,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -12406,6 +11893,8 @@ }, "node_modules/type-fest": { "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -12431,870 +11920,681 @@ "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "license": "MIT" - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unplugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", - "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/unplugin-utils/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/unplugin/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", - "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/uvu": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/vaul-vue": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.4.1.tgz", - "integrity": "sha512-A6jOWOZX5yvyo1qMn7IveoWN91mJI5L3BUKsIwkg6qrTGgHs1Sb1JF/vyLJgnbN1rH4OOOxFbtqL9A46bOyGUQ==", - "dependencies": { - "@vueuse/core": "^10.8.0", - "reka-ui": "^2.0.0", - "vue": "^3.4.5" - }, - "peerDependencies": { - "reka-ui": "^2.0.0", - "vue": "^3.3.0" - } - }, - "node_modules/vaul-vue/node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/vaul-vue/node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/vaul-vue/node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/vaul-vue/node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, - "node_modules/vaul-vue/node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, "license": "MIT", "dependencies": { - "vue-demi": ">=0.14.8" + "@types/unist": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vaul-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" + "dependencies": { + "@types/unist": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vee-validate": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", - "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-api": "^7.5.2", - "type-fest": "^4.8.3" + "@types/unist": "^3.0.0" }, - "peerDependencies": { - "vue": "^3.4.26" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" + "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/vite": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", - "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.16", - "rolldown": "~1.1.4", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" }, "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { + "@farmfe/core": { "optional": true }, - "esbuild": { + "@rspack/core": { "optional": true }, - "jiti": { + "bun-types-no-globals": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "sass": { + "rolldown": { "optional": true }, - "sass-embedded": { + "rollup": { "optional": true }, - "stylus": { + "unloader": { "optional": true }, - "sugarss": { + "vite": { "optional": true }, - "terser": { + "webpack": { "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, - "tsx": { - "optional": true + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" }, - "yaml": { - "optional": true + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vite-plugin-vue-devtools": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.1.5.tgz", - "integrity": "sha512-QSVntSN0sbVV08CZntMWOfF8McyPyYyYd19sChLjxYyFCC7Fcpey74ztw1uCCU23wTmR2yWPb92uaxVD4Lw0Fg==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "license": "MIT", "dependencies": { - "@vue/devtools-core": "^8.1.5", - "@vue/devtools-kit": "^8.1.5", - "@vue/devtools-shared": "^8.1.5", - "sirv": "^3.0.2", - "vite-plugin-inspect": "^11.3.3", - "vite-plugin-vue-inspector": "^6.0.0" + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" }, - "engines": { - "node": ">=v14.21.3" + "bin": { + "uvu": "bin.js" }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-kit": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", - "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", - "dev": true, - "license": "MIT", + "node_modules/vaul-vue": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.4.1.tgz", + "integrity": "sha512-A6jOWOZX5yvyo1qMn7IveoWN91mJI5L3BUKsIwkg6qrTGgHs1Sb1JF/vyLJgnbN1rH4OOOxFbtqL9A46bOyGUQ==", "dependencies": { - "@vue/devtools-shared": "^8.1.5", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" + "@vueuse/core": "^10.8.0", + "reka-ui": "^2.0.0", + "vue": "^3.4.5" + }, + "peerDependencies": { + "reka-ui": "^2.0.0", + "vue": "^3.3.0" } }, - "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-shared": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", - "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite-plugin-vue-devtools/node_modules/perfect-debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", - "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", - "dev": true, + "node_modules/vaul-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "license": "MIT" }, - "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", - "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", - "dev": true, + "node_modules/vaul-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", "license": "MIT", "dependencies": { - "ansis": "^4.1.0", - "debug": "^4.4.1", - "error-stack-parser-es": "^1.0.5", - "ohash": "^2.0.11", - "open": "^10.2.0", - "perfect-debounce": "^2.0.0", - "sirv": "^3.0.1", - "unplugin-utils": "^0.3.0", - "vite-dev-rpc": "^1.1.0" + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vaul-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" }, "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0-0" + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" }, "peerDependenciesMeta": { - "@nuxt/kit": { + "@vue/composition-api": { "optional": true } } }, - "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect/node_modules/vite-dev-rpc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", - "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", - "dev": true, + "node_modules/vaul-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vaul-vue/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", "license": "MIT", "dependencies": { - "birpc": "^2.4.0", - "vite-hot-client": "^2.1.0" + "vue-demi": ">=0.14.8" }, "funding": { "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" } }, - "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect/node_modules/vite-dev-rpc/node_modules/vite-hot-client": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", - "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", - "dev": true, + "node_modules/vaul-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } } }, - "node_modules/vite-plugin-vue-inspector": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz", - "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==", - "dev": true, + "node_modules/vee-validate": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", + "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", "license": "MIT", "dependencies": { - "@babel/core": "^7.23.0", - "@babel/plugin-proposal-decorators": "^7.23.0", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.22.15", - "@vue/babel-plugin-jsx": "^1.1.5", - "@vue/compiler-dom": "^3.3.4", - "kolorist": "^1.8.0", - "magic-string": "^0.30.4" + "@vue/devtools-api": "^7.5.2", + "type-fest": "^4.8.3" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "vue": "^3.4.26" } }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, + "node_modules/vee-validate/node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/vite/node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "license": "MPL-2.0", "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "@vue/devtools-kit": "^7.7.10" } }, - "node_modules/vite/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node_modules/vee-validate/node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" } }, - "node_modules/vite/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node_modules/vee-validate/node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" } }, - "node_modules/vite/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "node_modules/vee-validate/node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" }, - "node_modules/vite/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 12.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/vite-dev-rpc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" } }, - "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, + "node_modules/vite-dev-rpc/node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "dev": true, + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, + "node_modules/vite-hot-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", + "dev": true, + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" } }, - "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/vite-plugin-inspect": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.0", + "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", + "ohash": "^2.0.11", + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } } }, - "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/vite-plugin-vue-devtools": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.2.1.tgz", + "integrity": "sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.2.1", + "@vue/devtools-kit": "^8.2.1", + "@vue/devtools-shared": "^8.2.1", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^6.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=v14.21.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz", + "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/vitepress": { @@ -13344,82 +12644,6 @@ } } }, - "node_modules/vitepress/node_modules/@vue/devtools-api": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.1.tgz", - "integrity": "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.1.1" - } - }, - "node_modules/vitepress/node_modules/@vue/devtools-kit": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.1.tgz", - "integrity": "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.1.1", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" - } - }, - "node_modules/vitepress/node_modules/@vue/devtools-shared": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.1.tgz", - "integrity": "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitepress/node_modules/@vueuse/core": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", - "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.1", - "@vueuse/shared": "14.2.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/vitepress/node_modules/@vueuse/metadata": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", - "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/vitepress/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/vitepress/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -13435,34 +12659,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vitepress/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitepress/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitepress/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -13531,19 +12735,19 @@ } }, "node_modules/vitest": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", - "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.7", - "@vitest/mocker": "4.1.7", - "@vitest/pretty-format": "4.1.7", - "@vitest/runner": "4.1.7", - "@vitest/snapshot": "4.1.7", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -13571,12 +12775,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.7", - "@vitest/browser-preview": "4.1.7", - "@vitest/browser-webdriverio": "4.1.7", - "@vitest/coverage-istanbul": "4.1.7", - "@vitest/coverage-v8": "4.1.7", - "@vitest/ui": "4.1.7", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -13620,19 +12824,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -13653,16 +12844,16 @@ } }, "node_modules/vue": { - "version": "3.5.39", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", - "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-sfc": "3.5.39", - "@vue/runtime-dom": "3.5.39", - "@vue/server-renderer": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" }, "peerDependencies": { "typescript": "*" @@ -13685,14 +12876,16 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "2.2.12", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.8.tgz", + "integrity": "sha512-troqCMmQodQDqUqn63NQaFi+CDSclSe7sc8VEBFqf5GFLqmGR2Ph3P2WEC7qwpRVyEWsTi/aAr4vyOe/B1hU3g==", "dev": true, "license": "MIT" }, "node_modules/vue-eslint-parser": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz", - "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz", + "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", "dev": true, "license": "MIT", "dependencies": { @@ -13727,37 +12920,39 @@ } }, "node_modules/vue-router": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.7.tgz", - "integrity": "sha512-dqfk8kvRbCutmCOCj/XLDqDEYxc1wBdAOGLuVy5M93ifYMsBd5fIjfaPN4tQAbxr5IprdBDIox1gr4wYyOx/SA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", + "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", "license": "MIT", "dependencies": { - "@babel/generator": "^8.0.0-rc.4", - "@vue-macros/common": "^3.1.1", - "@vue/devtools-api": "^8.1.1", - "ast-walker-scope": "^0.8.3", + "@babel/generator": "^8.0.0", + "@vue-macros/common": "^3.1.3", + "@vue/devtools-api": "^8.1.5", + "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", - "local-pkg": "^1.1.2", + "local-pkg": "^1.2.1", "magic-string": "^0.30.21", - "mlly": "^1.8.0", + "mlly": "^1.8.2", "muggle-string": "^0.4.1", + "nostics": "^1.1.4", "pathe": "^2.0.3", - "picomatch": "^4.0.3", + "picomatch": "^4.0.5", "scule": "^1.3.0", - "tinyglobby": "^0.2.15", - "unplugin": "^3.0.0", - "unplugin-utils": "^0.3.1", - "yaml": "^2.8.2" + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2", + "yaml": "^2.9.0" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", - "@vue/compiler-sfc": "^3.5.34", - "pinia": "^3.0.4", - "vue": "^3.5.34" + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" }, "peerDependenciesMeta": { "@pinia/colada": { @@ -13768,17 +12963,20 @@ }, "pinia": { "optional": true + }, + "vite": { + "optional": true } } }, "node_modules/vue-router/node_modules/@babel/generator": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.5.tgz", - "integrity": "sha512-nFZPWz3FHIS7y6rMIVoa/WBwjdutfIaRJIBQjzn+t3RnecZoRNlGmGcyR2wb0T/IgSd50Kz/6dG8/LvMCRunjg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", "license": "MIT", "dependencies": { - "@babel/parser": "^8.0.0-rc.5", - "@babel/types": "^8.0.0-rc.5", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", @@ -13789,30 +12987,30 @@ } }, "node_modules/vue-router/node_modules/@babel/helper-string-parser": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0-rc.5.tgz", - "integrity": "sha512-sN7R8rBvDurfaziNfDEIjIntlazmlkCDGO4SNl2RJ3wRCn+QxspLV7hzYAE8WWVd2joVuT8sUxeePdLp2idI1A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", "license": "MIT", "engines": { "node": "^22.18.0 || >=24.11.0" } }, "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.5.tgz", - "integrity": "sha512-ehJDxHvtbZ85RtX/L2fi0h9AGsBNqB5Euv1EB8RMAvGYvD+2X+QbpzzOpbklnNXO+WSZJNOaetw2BBj27xsWVg==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", "license": "MIT", "engines": { "node": "^22.18.0 || >=24.11.0" } }, "node_modules/vue-router/node_modules/@babel/parser": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.5.tgz", - "integrity": "sha512-/Mfg83rK3+jsRbl4Vbd0jqxc6M1A1/WNFtgrowRM1unEsD3XcNnrBdMM0JWakd0/RN9lseQKwPduW1TiEwKOlQ==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", "license": "MIT", "dependencies": { - "@babel/types": "^8.0.0-rc.5" + "@babel/types": "^8.0.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -13822,63 +13020,18 @@ } }, "node_modules/vue-router/node_modules/@babel/types": { - "version": "8.0.0-rc.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.0-rc.5.tgz", - "integrity": "sha512-JeSVu/m8x/zpp4CLjYHVNXuhEyOkhPXuxM8YOXjh6L4LlvQNKuUNOTo5KdBuKAcTDHw8DquToTaEkhsBqPXOaA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^8.0.0-rc.5", - "@babel/helper-validator-identifier": "^8.0.0-rc.5" + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" }, "engines": { "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.2.tgz", - "integrity": "sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^8.1.2" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-kit": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.2.tgz", - "integrity": "sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^8.1.2", - "birpc": "^2.6.1", - "hookable": "^5.5.3", - "perfect-debounce": "^2.0.0" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-shared": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.2.tgz", - "integrity": "sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==", - "license": "MIT" - }, - "node_modules/vue-router/node_modules/perfect-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", - "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "license": "MIT" - }, - "node_modules/vue-router/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vue-sonner": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-2.0.9.tgz", @@ -13902,14 +13055,14 @@ } }, "node_modules/vue-tsc": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.6.tgz", - "integrity": "sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.8.tgz", + "integrity": "sha512-xXmYlVQpcwJDWyGlqbHrGVOl1h3UOsASymRibrHc+iy9j/UNnOrOn4u+fntHz4D6Cs74RtapeqVV6CzJeg+UlA==", "dev": true, "license": "MIT", "dependencies": { "@volar/typescript": "2.4.28", - "@vue/language-core": "3.2.6" + "@vue/language-core": "3.3.8" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -13919,9 +13072,9 @@ } }, "node_modules/webdriver-bidi-protocol": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", - "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz", + "integrity": "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==", "dev": true, "license": "Apache-2.0" }, @@ -13933,6 +13086,8 @@ }, "node_modules/whatwg-mimetype": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, "license": "MIT", "engines": { @@ -13940,11 +13095,13 @@ } }, "node_modules/whence": { - "version": "2.0.2", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/whence/-/whence-2.1.0.tgz", + "integrity": "sha512-4UBPMg5mng5KLzdliVQdQ4fJwCdIMXkE8CkoDmGKRy5r8pV9xq+nVgf/sKXpmNEIOtFp7m7v2bFdb7JoLvh+Hg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.7", - "eval-estree-expression": "^2.1.1" + "@babel/parser": "^7.28.0", + "eval-estree-expression": "^3.0.0" }, "engines": { "node": ">=14" @@ -13952,6 +13109,8 @@ }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -13966,6 +13125,8 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -13981,6 +13142,8 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -13989,21 +13152,23 @@ }, "node_modules/wordwrap": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=20" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -14012,6 +13177,8 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14028,27 +13195,41 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -14062,6 +13243,8 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -14071,30 +13254,10 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "dev": true, "license": "MIT", "engines": { @@ -14114,27 +13277,30 @@ } }, "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "dev": true, "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/xml-name-validator": { - "version": "4.0.0", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/y18n": { @@ -14147,10 +13313,16 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -14163,91 +13335,54 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=20" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { diff --git a/frontend/package.json b/frontend/package.json index 906dd9086..082f9463c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "@types/uuid": "^11.0.0", "@unovis/vue": "^1.5.2", "@vee-validate/zod": "^4.15.1", - "@vueuse/core": "^13.9.0", + "@vueuse/core": "^14.3.0", "@zodios/core": "^10.9.6", "axios": "^1.16.1", "class-variance-authority": "^0.7.1", @@ -57,8 +57,8 @@ "lodash-es": "^4.18.1", "lucide-vue-next": "^1.0.0", "openapi-zod-client": "^1.18.3", - "pdf-vue3": "^1.0.12", - "pinia": "^3.0.1", + "pdf-vue3": "^2.0.1", + "pinia": "^4.0.2", "qs": "^6.15.2", "quill": "^2.0.3", "reka-ui": "^2.8.2", @@ -83,7 +83,7 @@ "@types/debug": "^4.1.13", "@types/dompurify": "^3.2.0", "@types/js-cookie": "^3.0.6", - "@types/node": "^25.9.3", + "@types/node": "^26.1.2", "@typescript-eslint/eslint-plugin": "^8.59.4", "@typescript-eslint/parser": "^8.64.0", "@vitejs/plugin-vue": "^6.0.8", @@ -95,12 +95,12 @@ "codesight": "^1.18.0", "dotenv": "^17.4.2", "eslint": "^10.7.0", - "eslint-plugin-vue": "~10.8.0", + "eslint-plugin-vue": "~10.10.0", "happy-dom": "^20.8.9", "jiti": "^2.4.2", "lint-staged": "^17.0.3", "npm-run-all2": "^8.0.4", - "prettier": "3.8.3", + "prettier": "3.9.6", "puppeteer": "^25.0.2", "tsx": "^4.21.0", "typescript": "~6.0.3", @@ -111,6 +111,9 @@ "vue-tsc": "^3.2.6" }, "overrides": { + "@kodeglot/vue-calendar": { + "pinia": "$pinia" + }, "basic-ftp": "^5.3.0", "brace-expansion@1": "^1.1.14", "brace-expansion@2": "^2.0.3" diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 5622fcf0a..26736d5b1 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -42,12 +42,17 @@ export default defineConfig({ baseURL, trace: 'on', // Always capture traces for timing analysis screenshot: 'only-on-failure', - actionTimeout: 30000, - navigationTimeout: 60000, + // 0 = no per-action cap; the test-level timeout below is the only hard + // limit, matching what expectStepUnder already documents. A per-action cap + // throws away the diagnosis: a click that dies at 30s might merely have + // been slow, but one that still fails after the full 120s cannot be — so + // capping early leaves "it was just slow" permanently unfalsifiable. + actionTimeout: 0, + navigationTimeout: 0, }, - // Test-level safety net — generous to avoid false timeouts on slow UAT hardware. - // Individual operations use narrower timeouts; this just prevents runaway hangs. + // The single hard timeout. Generous so that reaching it is evidence the page + // is broken rather than slow, not a false positive on slow hardware. timeout: 120000, projects: [ diff --git a/frontend/schema.yml b/frontend/schema.yml index 6bf59f665..93bfbbd02 100644 --- a/frontend/schema.yml +++ b/frontend/schema.yml @@ -610,6 +610,7 @@ paths: - accounts security: - cookieAuth: [] + - {} responses: '200': content: @@ -1855,95 +1856,6 @@ paths: schema: $ref: '#/components/schemas/CompanyErrorResponse' description: '' - /api/companies/jobs/{job_id}/person/: - get: - operationId: companies_jobs_person_retrieve - description: Retrieve person information for a specific job. - summary: Get job person - parameters: - - in: path - name: job_id - schema: - type: string - format: uuid - description: UUID of the job - required: true - tags: - - Companies - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/JobPersonResponse' - description: '' - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/CompanyErrorResponse' - description: '' - '500': - content: - application/json: - schema: - $ref: '#/components/schemas/CompanyErrorResponse' - description: '' - put: - operationId: companies_jobs_person_update - description: Update the person associated with a specific job. - summary: Update job person - parameters: - - in: path - name: job_id - schema: - type: string - format: uuid - description: UUID of the job - required: true - tags: - - Companies - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JobPersonUpdateRequest' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/JobPersonUpdateRequest' - multipart/form-data: - schema: - $ref: '#/components/schemas/JobPersonUpdateRequest' - required: true - security: - - cookieAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/JobPersonResponse' - description: '' - '400': - content: - application/json: - schema: - $ref: '#/components/schemas/CompanyErrorResponse' - description: '' - '404': - content: - application/json: - schema: - $ref: '#/components/schemas/CompanyErrorResponse' - description: '' - '500': - content: - application/json: - schema: - $ref: '#/components/schemas/CompanyErrorResponse' - description: '' /api/companies/pickup-addresses/: get: operationId: companies_pickup_addresses_list @@ -4084,6 +3996,74 @@ paths: schema: $ref: '#/components/schemas/JobFileThumbnailErrorResponse' description: '' + /api/job/jobs/{job_id}/finish/: + get: + operationId: job_jobs_finish_retrieve + description: Fetch the authoritative Finish Job customer balance and completion + checklist for a job. All currency values are calculated server-side. + parameters: + - in: path + name: job_id + schema: + type: string + format: uuid + required: true + tags: + - Jobs + security: + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/JobFinishResponse' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/JobRestErrorResponse' + description: '' + patch: + operationId: job_jobs_finish_partial_update + description: Update one or more completion checklist items. Each changed item + adds a job-history event. Unknown item keys are rejected. + parameters: + - in: path + name: job_id + schema: + type: string + format: uuid + required: true + tags: + - Jobs + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedJobCompletionChecklistUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedJobCompletionChecklistUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedJobCompletionChecklistUpdateRequest' + security: + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/JobFinishResponse' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/JobRestErrorResponse' + description: '' /api/job/jobs/{job_id}/header/: get: operationId: job_jobs_header_retrieve @@ -5166,6 +5146,34 @@ paths: schema: $ref: '#/components/schemas/FetchJobsResponse' description: '' + /api/job/jobs/kanban-changes/: + get: + operationId: getKanbanChanges + description: Return changed Kanban cards after an opaque Job dataset version. + parameters: + - in: query + name: after + schema: + type: string + description: Opaque previous kanban dataset version + required: true + tags: + - job + security: + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/KanbanChangesResponse' + description: '' + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/KanbanErrorResponse' + description: '' /api/job/jobs/status-choices/: get: operationId: job_jobs_status_choices_retrieve @@ -7222,7 +7230,25 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/DeliveryReceiptResponse' + $ref: '#/components/schemas/PurchaseOrderMutationErrorResponse' + description: '' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/PurchaseOrderMutationErrorResponse' + description: '' + '412': + content: + application/json: + schema: + $ref: '#/components/schemas/PurchaseOrderMutationErrorResponse' + description: '' + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/PurchaseOrderMutationErrorResponse' description: '' /api/purchasing/jobs/: get: @@ -10057,6 +10083,7 @@ components: * `OpenAI` - Openai model_name: type: string + nullable: true description: Model name (e.g., gemini-flash-latest) maxLength: 100 default: @@ -10088,6 +10115,7 @@ components: * `OpenAI` - Openai model_name: type: string + nullable: true description: Model name (e.g., gemini-flash-latest) maxLength: 100 default: @@ -10119,6 +10147,8 @@ components: * `OpenAI` - Openai model_name: type: string + nullable: true + minLength: 1 description: Model name (e.g., gemini-flash-latest) maxLength: 100 default: @@ -10127,8 +10157,8 @@ components: api_key: type: string writeOnly: true - description: API Key for the provider. Leave blank to keep unchanged on - update. + minLength: 1 + description: API Key for the provider. Omit to keep unchanged on update. required: - name - provider_type @@ -10155,6 +10185,8 @@ components: * `OpenAI` - Openai model_name: type: string + nullable: true + minLength: 1 description: Model name (e.g., gemini-flash-latest) maxLength: 100 default: @@ -10382,6 +10414,15 @@ components: - id - message - timestamp + AppErrorDetails: + type: object + description: Reference to the persisted failure backing an API error response. + properties: + error_id: + type: string + format: uuid + required: + - error_id AppErrorListResponse: type: object description: Serializer for paginated AppError list response. @@ -10413,14 +10454,17 @@ components: app: type: string nullable: true + minLength: 1 maxLength: 50 file: type: string nullable: true + minLength: 1 maxLength: 200 function: type: string nullable: true + minLength: 1 maxLength: 100 severity: type: integer @@ -10621,9 +10665,6 @@ components: required: - id - name - BlankEnum: - enum: - - '' BrokenFKReference: type: object description: Details of a broken foreign key reference. @@ -10754,6 +10795,7 @@ components: type: string format: email nullable: true + minLength: 1 phone: type: string nullable: true @@ -10784,6 +10826,7 @@ components: - success CompanyDefaults: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: integer @@ -10821,6 +10864,14 @@ components: minimum: -1000 exclusiveMaximum: true exclusiveMinimum: true + gst_rate: + type: number + format: double + maximum: 0.9999 + minimum: 0 + description: Sales tax rate applied to amounts DocketWorks quotes before + Xero has issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero + remains authoritative for tax on invoices that exist. wage_rate: type: number format: double @@ -11173,6 +11224,7 @@ components: - wage_rate CompanyDefaultsRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: logo: type: string @@ -11191,6 +11243,7 @@ components: company_acronym: type: string nullable: true + minLength: 1 description: Short acronym for the company (e.g., 'MSM' for Morris Sheetmetal) maxLength: 10 time_markup: @@ -11207,6 +11260,14 @@ components: minimum: -1000 exclusiveMaximum: true exclusiveMinimum: true + gst_rate: + type: number + format: double + maximum: 0.9999 + minimum: 0 + description: Sales tax rate applied to amounts DocketWorks quotes before + Xero has issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero + remains authoritative for tax on invoices that exist. wage_rate: type: number format: double @@ -11260,42 +11321,50 @@ components: type: string format: uri nullable: true + minLength: 1 description: URL to the master Google Sheets quote template maxLength: 200 master_quote_template_id: type: string nullable: true + minLength: 1 description: Google Sheets ID for the quote template maxLength: 100 gdrive_quotes_folder_url: type: string format: uri nullable: true + minLength: 1 description: URL to the Google Drive folder for storing quotes maxLength: 200 gdrive_quotes_folder_id: type: string nullable: true + minLength: 1 description: Google Drive folder ID for storing quotes maxLength: 100 google_shared_drive_id: type: string nullable: true + minLength: 1 description: Google Shared Drive ID for the company shared drive maxLength: 100 gdrive_how_we_work_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '01 - How we work' (policies, basics) maxLength: 100 gdrive_sops_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '02 - SOPs' (standard operating procedures) maxLength: 100 gdrive_reference_library_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '03 - Reference Library' (reference documents, forms, registers) maxLength: 100 @@ -11307,11 +11376,13 @@ components: xero_tenant_id: type: string nullable: true + minLength: 1 description: The Xero tenant ID to use for this company maxLength: 100 xero_shortcode: type: string nullable: true + minLength: 1 description: Xero organisation shortcode for deep linking (e.g., '!8-5Xl') maxLength: 20 xero_sales_branding_theme_id: @@ -11401,26 +11472,31 @@ components: address_line1: type: string nullable: true + minLength: 1 description: Street address line 1 maxLength: 255 address_line2: type: string nullable: true + minLength: 1 description: Street address line 2 (optional) maxLength: 255 suburb: type: string nullable: true + minLength: 1 description: Suburb (for NZ addresses) maxLength: 100 city: type: string nullable: true + minLength: 1 description: City maxLength: 100 post_code: type: string nullable: true + minLength: 1 description: Postal/ZIP code maxLength: 20 country: @@ -11432,17 +11508,20 @@ components: type: string format: email nullable: true + minLength: 1 description: Company contact email address maxLength: 254 company_url: type: string format: uri nullable: true + minLength: 1 description: Company website URL maxLength: 200 test_company_name: type: string nullable: true + minLength: 1 description: Name of the test company used for testing (e.g., 'ABC Carpet Cleaning TEST IGNORE'). This company's name is preserved during data backports. maxLength: 255 @@ -11710,15 +11789,18 @@ components: position: type: string nullable: true + minLength: 1 maxLength: 255 notes: type: string nullable: true + minLength: 1 is_primary: type: boolean default: false CompanyNameOnly: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -11732,6 +11814,7 @@ components: - name CompanyPerson: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: person_id: type: string @@ -11776,6 +11859,7 @@ components: type: string format: email nullable: true + minLength: 1 phone: type: string nullable: true @@ -11864,6 +11948,8 @@ components: email: type: string format: email + nullable: true + minLength: 1 phone: type: string nullable: true @@ -11889,6 +11975,7 @@ components: - success CompleteJob: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -11973,6 +12060,7 @@ components: readOnly: true label: type: string + nullable: true maxLength: 255 is_primary: type: boolean @@ -12017,6 +12105,8 @@ components: maxLength: 255 label: type: string + nullable: true + minLength: 1 maxLength: 255 is_primary: type: boolean @@ -12054,6 +12144,7 @@ components: $ref: '#/components/schemas/CostLineKindEnum' desc: type: string + nullable: true description: Description of this cost line maxLength: 255 quantity: @@ -12177,6 +12268,8 @@ components: $ref: '#/components/schemas/CostLineKindEnum' desc: type: string + nullable: true + minLength: 1 description: Description of this cost line maxLength: 255 quantity: @@ -12583,11 +12676,14 @@ components: type: string kanban: type: string + kanban_related: + type: string crm_calls: type: string required: - crm_calls - kanban + - kanban_related - stock Day: type: object @@ -13250,6 +13346,88 @@ components: required: - end_date - start_date + FinishJobSummary: + type: object + description: |- + The authoritative customer balance shown in the Finish Job workspace. + + Read-only: every value is calculated by + apps.accounting.services.finish_job_summary, and the frontend formats rather + than recomputes them (ADR 0020). + properties: + job_value_excl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + valid_invoiced_excl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + outstanding_invoiced_incl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + remaining_to_invoice_excl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + remaining_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + remaining_to_invoice_incl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + total_to_pay_incl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + over_invoiced_excl_gst: + type: number + format: double + maximum: 10000000000 + minimum: -10000000000 + exclusiveMaximum: true + exclusiveMinimum: true + readOnly: true + required: + - job_value_excl_gst + - outstanding_invoiced_incl_gst + - over_invoiced_excl_gst + - remaining_gst + - remaining_to_invoice_excl_gst + - remaining_to_invoice_incl_gst + - total_to_pay_incl_gst + - valid_invoiced_excl_gst FormCreateRequest: type: object description: Request serializer for creating a form document (no Google Doc). @@ -13471,6 +13649,7 @@ components: document_number: type: string nullable: true + minLength: 1 maxLength: 50 tags: description: Free-text tags, e.g. ["safety", "inspection"] @@ -13736,6 +13915,7 @@ components: - improved_text Invoice: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -13799,6 +13979,7 @@ components: * `PAID` - Paid Job: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -13953,7 +14134,6 @@ components: * `supporting_rd` - Supporting R&D oneOf: - $ref: '#/components/schemas/RdtiTypeEnum' - - $ref: '#/components/schemas/BlankEnum' - $ref: '#/components/schemas/NullEnum' default_xero_pay_item_id: type: string @@ -14139,6 +14319,37 @@ components: - job_name - job_number - revenue + JobCompletionChecklist: + type: object + description: |- + Read shape for the front-desk completion checklist. + + The items are Job fields, so each tick is audited by the job's own + field-change machinery. Who ticked what, and when, is in the job history. + properties: + foreman_signed_off: + type: boolean + readOnly: true + timesheets_collected: + type: boolean + readOnly: true + materials_checked: + type: boolean + readOnly: true + customer_called: + type: boolean + readOnly: true + released: + type: boolean + readOnly: true + description: The job has left our possession, by collection, delivery or + on-site install. + required: + - customer_called + - foreman_signed_off + - materials_checked + - released + - timesheets_collected JobCostSetSummary: type: object description: Serializer for cost set summary data in job views @@ -14442,6 +14653,7 @@ components: delta_checksum: type: string readOnly: true + nullable: true detail: readOnly: true description: Structured audit data for this event. Keys vary by event_type. @@ -14508,6 +14720,7 @@ components: - events JobFile: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -14517,6 +14730,7 @@ components: maxLength: 255 mime_type: type: string + nullable: true maxLength: 100 uploaded_at: type: string @@ -14557,6 +14771,7 @@ components: - message JobFileRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -14567,6 +14782,8 @@ components: maxLength: 255 mime_type: type: string + nullable: true + minLength: 1 maxLength: 100 status: $ref: '#/components/schemas/JobFileStatusEnum' @@ -14658,6 +14875,21 @@ components: required: - message - uploaded + JobFinishResponse: + type: object + description: Everything the Finish Job workspace reads in one request. + properties: + summary: + allOf: + - $ref: '#/components/schemas/FinishJobSummary' + readOnly: true + checklist: + allOf: + - $ref: '#/components/schemas/JobCompletionChecklist' + readOnly: true + required: + - checklist + - summary JobForPurchasing: type: object description: Serializer for Job model in purchasing contexts. @@ -14805,7 +15037,6 @@ components: * `supporting_rd` - Supporting R&D oneOf: - $ref: '#/components/schemas/RdtiTypeEnum' - - $ref: '#/components/schemas/BlankEnum' - $ref: '#/components/schemas/NullEnum' min_people: type: integer @@ -14919,39 +15150,6 @@ components: - total_actual_profit - total_estimated_profit - total_profit - JobPersonResponse: - type: object - description: Serializer for job person information response - properties: - id: - type: string - format: uuid - name: - type: string - email: - type: string - nullable: true - required: - - email - - id - - name - JobPersonUpdateRequest: - type: object - description: Serializer for job person update request - properties: - id: - type: string - format: uuid - name: - type: string - minLength: 1 - email: - type: string - nullable: true - required: - - email - - id - - name JobProfitabilityItem: type: object description: Per-job profitability data. @@ -15284,6 +15482,8 @@ components: properties: error: type: string + details: + $ref: '#/components/schemas/AppErrorDetails' required: - error JobStatusChoicesResponse: @@ -15482,7 +15682,6 @@ components: * `supporting_rd` - Supporting R&D oneOf: - $ref: '#/components/schemas/RdtiTypeEnum' - - $ref: '#/components/schemas/BlankEnum' - $ref: '#/components/schemas/NullEnum' default_xero_pay_item_id: type: string @@ -15934,6 +16133,30 @@ components: - kpi_daily_gp_green - kpi_daily_gp_target - kpi_daily_shop_hours_percentage + KanbanChangesResponse: + type: object + description: Serializer for incremental Kanban freshness reconciliation. + properties: + success: + type: boolean + jobs: + type: array + items: + $ref: '#/components/schemas/KanbanColumnJob' + removed_job_ids: + type: array + items: + type: string + format: uuid + full_refresh_required: + type: boolean + error: + type: string + required: + - full_refresh_required + - jobs + - removed_job_ids + - success KanbanColumnJob: type: object description: |- @@ -16036,11 +16259,13 @@ components: type: object description: Serializer for error kanban operation response. properties: + error: + type: string + details: + $ref: '#/components/schemas/AppErrorDetails' success: type: boolean default: false - error: - type: string required: - error KanbanJob: @@ -16155,6 +16380,7 @@ components: - id KanbanStaff: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -16321,6 +16547,8 @@ components: template_url: type: string format: uri + nullable: true + minLength: 1 LinkQuoteSheetResponse: type: object description: Serializer for link quote sheet response. @@ -16346,7 +16574,6 @@ components: - titanium - zinc - galvanized - - unspecified - other type: string description: |- @@ -16358,7 +16585,6 @@ components: * `titanium` - Titanium * `zinc` - Zinc * `galvanized` - Galvanized - * `unspecified` - Unspecified * `other` - Other ModernStaff: type: object @@ -17036,6 +17262,8 @@ components: * `OpenAI` - Openai model_name: type: string + nullable: true + minLength: 1 description: Model name (e.g., gemini-flash-latest) maxLength: 100 default: @@ -17044,10 +17272,11 @@ components: api_key: type: string writeOnly: true - description: API Key for the provider. Leave blank to keep unchanged on - update. + minLength: 1 + description: API Key for the provider. Omit to keep unchanged on update. PatchedCompanyDefaultsRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: logo: type: string @@ -17066,6 +17295,7 @@ components: company_acronym: type: string nullable: true + minLength: 1 description: Short acronym for the company (e.g., 'MSM' for Morris Sheetmetal) maxLength: 10 time_markup: @@ -17082,6 +17312,14 @@ components: minimum: -1000 exclusiveMaximum: true exclusiveMinimum: true + gst_rate: + type: number + format: double + maximum: 0.9999 + minimum: 0 + description: Sales tax rate applied to amounts DocketWorks quotes before + Xero has issued an invoice, as a fraction (0.1500 = 15% NZ GST). Xero + remains authoritative for tax on invoices that exist. wage_rate: type: number format: double @@ -17135,42 +17373,50 @@ components: type: string format: uri nullable: true + minLength: 1 description: URL to the master Google Sheets quote template maxLength: 200 master_quote_template_id: type: string nullable: true + minLength: 1 description: Google Sheets ID for the quote template maxLength: 100 gdrive_quotes_folder_url: type: string format: uri nullable: true + minLength: 1 description: URL to the Google Drive folder for storing quotes maxLength: 200 gdrive_quotes_folder_id: type: string nullable: true + minLength: 1 description: Google Drive folder ID for storing quotes maxLength: 100 google_shared_drive_id: type: string nullable: true + minLength: 1 description: Google Shared Drive ID for the company shared drive maxLength: 100 gdrive_how_we_work_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '01 - How we work' (policies, basics) maxLength: 100 gdrive_sops_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '02 - SOPs' (standard operating procedures) maxLength: 100 gdrive_reference_library_folder_id: type: string nullable: true + minLength: 1 description: Folder ID for '03 - Reference Library' (reference documents, forms, registers) maxLength: 100 @@ -17182,11 +17428,13 @@ components: xero_tenant_id: type: string nullable: true + minLength: 1 description: The Xero tenant ID to use for this company maxLength: 100 xero_shortcode: type: string nullable: true + minLength: 1 description: Xero organisation shortcode for deep linking (e.g., '!8-5Xl') maxLength: 20 xero_sales_branding_theme_id: @@ -17276,26 +17524,31 @@ components: address_line1: type: string nullable: true + minLength: 1 description: Street address line 1 maxLength: 255 address_line2: type: string nullable: true + minLength: 1 description: Street address line 2 (optional) maxLength: 255 suburb: type: string nullable: true + minLength: 1 description: Suburb (for NZ addresses) maxLength: 100 city: type: string nullable: true + minLength: 1 description: City maxLength: 100 post_code: type: string nullable: true + minLength: 1 description: Postal/ZIP code maxLength: 20 country: @@ -17307,17 +17560,20 @@ components: type: string format: email nullable: true + minLength: 1 description: Company contact email address maxLength: 254 company_url: type: string format: uri nullable: true + minLength: 1 description: Company website URL maxLength: 200 test_company_name: type: string nullable: true + minLength: 1 description: Name of the test company used for testing (e.g., 'ABC Carpet Cleaning TEST IGNORE'). This company's name is preserved during data backports. maxLength: 255 @@ -17411,6 +17667,8 @@ components: email: type: string format: email + nullable: true + minLength: 1 phone: type: string nullable: true @@ -17440,6 +17698,8 @@ components: maxLength: 255 label: type: string + nullable: true + minLength: 1 maxLength: 255 is_primary: type: boolean @@ -17454,6 +17714,8 @@ components: $ref: '#/components/schemas/CostLineKindEnum' desc: type: string + nullable: true + minLength: 1 description: Description of this cost line maxLength: 255 quantity: @@ -17535,6 +17797,7 @@ components: document_number: type: string nullable: true + minLength: 1 maxLength: 50 tags: description: Free-text tags, e.g. ["safety", "inspection"] @@ -17542,6 +17805,26 @@ components: description: JSON schema defining entry fields for form templates status: $ref: '#/components/schemas/FormStatusEnum' + PatchedJobCompletionChecklistUpdateRequest: + type: object + description: |- + Partial update shape: send only the items being changed. + + Unknown keys are rejected rather than dropped, so a client typo is a 400 + instead of a silent no-op. + properties: + foreman_signed_off: + type: boolean + timesheets_collected: + type: boolean + materials_checked: + type: boolean + customer_called: + type: boolean + released: + type: boolean + description: The job has left our possession, by collection, delivery or + on-site install. PatchedJobDeltaEnvelopeRequest: type: object description: Serializer that validates the delta envelope submitted by the frontend. @@ -17672,9 +17955,11 @@ components: default: false label: type: string - default: '' + nullable: true + minLength: 1 PatchedPersonIdentityUpdateRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: name: type: string @@ -17684,9 +17969,11 @@ components: type: string format: email nullable: true + minLength: 1 maxLength: 254 PatchedPhoneEndpointRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: number: type: string @@ -17704,12 +17991,15 @@ components: nullable: true provider_account_code: type: string + nullable: true + minLength: 1 maxLength: 100 provider_metadata: {} is_active: type: boolean PatchedPhoneProviderSettingsRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: downloads_enabled: type: boolean @@ -17719,15 +18009,20 @@ components: type: string format: uri nullable: true + minLength: 1 maxLength: 200 username: type: string writeOnly: true + minLength: 1 password: type: string writeOnly: true + minLength: 1 account_code: type: string + nullable: true + minLength: 1 maxLength: 100 PatchedProcedureUpdateRequest: type: object @@ -17740,12 +18035,15 @@ components: document_number: type: string nullable: true + minLength: 1 description: Document number (e.g. '307' for section 3, doc 7) maxLength: 50 tags: description: Free-text tags, e.g. ["safety", "machinery", "sop"] site_location: type: string + nullable: true + minLength: 1 description: Work site location maxLength: 500 status: @@ -17784,6 +18082,7 @@ components: $ref: '#/components/schemas/PurchaseOrderLineUpdateRequest' PatchedStaffRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: email: type: string @@ -17801,6 +18100,7 @@ components: preferred_name: type: string nullable: true + minLength: 1 maxLength: 30 base_wage_rate: type: number @@ -17814,6 +18114,7 @@ components: xero_user_id: type: string nullable: true + minLength: 1 maxLength: 255 date_left: type: string @@ -17910,6 +18211,7 @@ components: item_code: type: string nullable: true + minLength: 1 description: Xero Item Code maxLength: 255 description: @@ -17958,8 +18260,11 @@ components: * `product_catalog` - Product Catalog location: type: string + nullable: true + minLength: 1 description: Where we are keeping this metal_type: + nullable: true description: |- Type of metal @@ -17971,19 +18276,20 @@ components: * `titanium` - Titanium * `zinc` - Zinc * `galvanized` - Galvanized - * `unspecified` - Unspecified * `other` - Other oneOf: - $ref: '#/components/schemas/MetalTypeEnum' - - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' alloy: type: string nullable: true + minLength: 1 description: Alloy specification (e.g., 304, 6061) maxLength: 50 specifics: type: string nullable: true + minLength: 1 description: Specific details (e.g., m8 countersunk socket screw) maxLength: 255 is_active: @@ -18011,6 +18317,7 @@ components: suburb: type: string nullable: true + minLength: 1 description: Suburb or neighbourhood (e.g., Thorndon, Northcote Point) maxLength: 100 city: @@ -18021,11 +18328,13 @@ components: state: type: string nullable: true + minLength: 1 description: State or province maxLength: 100 postal_code: type: string nullable: true + minLength: 1 description: Postal or ZIP code maxLength: 20 country: @@ -18036,6 +18345,7 @@ components: google_place_id: type: string nullable: true + minLength: 1 description: Google Place ID for this address maxLength: 255 latitude: @@ -18062,6 +18372,7 @@ components: notes: type: string nullable: true + minLength: 1 description: Additional notes (e.g., opening hours, access instructions) PatchedWorkshopTimesheetEntryUpdateRequest: type: object @@ -18524,12 +18835,14 @@ components: default: false label: type: string - default: '' + nullable: true + minLength: 1 required: - method_type - value PersonDetail: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -18577,6 +18890,7 @@ components: - updated_at PersonIdentityUpdate: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: name: type: string @@ -18588,6 +18902,7 @@ components: maxLength: 254 PersonIdentityUpdateRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: name: type: string @@ -18597,9 +18912,11 @@ components: type: string format: email nullable: true + minLength: 1 maxLength: 254 PersonSummary: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -18640,6 +18957,7 @@ components: - job PhoneCallRecord: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -18666,18 +18984,23 @@ components: call_type: type: string readOnly: true + nullable: true status: type: string readOnly: true + nullable: true description: type: string readOnly: true + nullable: true origin: type: string readOnly: true + nullable: true destination: type: string readOnly: true + nullable: true direction: allOf: - $ref: '#/components/schemas/DirectionEnum' @@ -18685,9 +19008,11 @@ components: our_number: type: string readOnly: true + nullable: true external_number: type: string readOnly: true + nullable: true origin_endpoint: type: string format: uuid @@ -18806,6 +19131,7 @@ components: - updated_at PhoneCallRecording: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -18820,9 +19146,11 @@ components: filename: type: string readOnly: true + nullable: true content_type: type: string readOnly: true + nullable: true byte_size: type: integer readOnly: true @@ -18830,6 +19158,7 @@ components: sha256: type: string readOnly: true + nullable: true archived_at: type: string format: date-time @@ -18838,6 +19167,7 @@ components: archive_error: type: string readOnly: true + nullable: true provider_deleted_at: type: string format: date-time @@ -18846,6 +19176,7 @@ components: provider_delete_error: type: string readOnly: true + nullable: true local_deleted_at: type: string format: date-time @@ -18892,6 +19223,7 @@ components: - company_name PhoneEndpoint: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -18917,6 +19249,7 @@ components: readOnly: true provider_account_code: type: string + nullable: true maxLength: 100 provider_metadata: {} is_active: @@ -18940,6 +19273,7 @@ components: - updated_at PhoneEndpointRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: number: type: string @@ -18957,6 +19291,8 @@ components: nullable: true provider_account_code: type: string + nullable: true + minLength: 1 maxLength: 100 provider_metadata: {} is_active: @@ -19074,6 +19410,7 @@ components: - person_name PhoneProviderSettings: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: integer @@ -19095,6 +19432,7 @@ components: readOnly: true account_code: type: string + nullable: true maxLength: 100 created_at: type: string @@ -19288,16 +19626,19 @@ components: maxLength: 255 site_location: type: string + nullable: true description: Work site location maxLength: 500 google_doc_id: type: string readOnly: true + nullable: true description: Google Docs document ID google_doc_url: type: string format: uri readOnly: true + nullable: true description: URL to edit the document in Google Docs job_id: type: string @@ -19364,11 +19705,13 @@ components: maxLength: 255 site_location: type: string + nullable: true description: Work site location maxLength: 500 google_doc_url: type: string format: uri + nullable: true description: URL to edit the document in Google Docs maxLength: 200 job_number: @@ -19417,12 +19760,15 @@ components: document_number: type: string nullable: true + minLength: 1 description: Document number (e.g. '307' for section 3, doc 7) maxLength: 50 tags: description: Free-text tags, e.g. ["safety", "machinery", "sop"] site_location: type: string + nullable: true + minLength: 1 description: Work site location maxLength: 500 status: @@ -19895,7 +20241,6 @@ components: nullable: true oneOf: - $ref: '#/components/schemas/MetalTypeEnum' - - $ref: '#/components/schemas/BlankEnum' - $ref: '#/components/schemas/NullEnum' alloy: type: string @@ -20134,6 +20479,19 @@ components: - status - supplier - supplier_id + PurchaseOrderMutationErrorResponse: + type: object + description: Exception-derived error response for PO mutations and receipts. + properties: + error: + type: string + details: + $ref: '#/components/schemas/AppErrorDetails' + success: + type: boolean + default: false + required: + - error PurchaseOrderUpdate: type: object description: Serializer for updating purchase orders. @@ -20193,6 +20551,7 @@ components: - total_count Quote: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -20389,7 +20748,7 @@ components: description: Summary data for a single RDTI classification category. properties: rdti_type: - type: string + $ref: '#/components/schemas/RdtiTypeEnum' label: type: string hours: @@ -20423,7 +20782,7 @@ components: company_name: type: string rdti_type: - type: string + $ref: '#/components/schemas/RdtiTypeEnum' hours: type: number format: double @@ -21171,6 +21530,7 @@ components: - success SessionReplayChunk: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -21325,6 +21685,7 @@ components: - results SessionReplayRecording: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -21365,6 +21726,7 @@ components: user_agent: type: string readOnly: true + nullable: true viewport_width: type: integer readOnly: true @@ -21472,6 +21834,7 @@ components: * `quality` - Quality - Prioritize Quality Staff: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string @@ -21596,11 +21959,6 @@ components: type: string format: date-time readOnly: true - last_login: - type: string - format: date-time - readOnly: true - nullable: true groups: type: array items: @@ -21623,12 +21981,12 @@ components: - first_name - icon_url - id - - last_login - last_name - updated_at - wage_rate StaffCreateRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: email: type: string @@ -21646,6 +22004,7 @@ components: preferred_name: type: string nullable: true + minLength: 1 maxLength: 30 base_wage_rate: type: number @@ -21659,6 +22018,7 @@ components: xero_user_id: type: string nullable: true + minLength: 1 maxLength: 255 date_left: type: string @@ -21738,10 +22098,6 @@ components: created_at: type: string format: date-time - last_login: - type: string - format: date-time - nullable: true groups: type: array items: @@ -22039,6 +22395,7 @@ components: - wage_rate StaffRequest: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: email: type: string @@ -22056,6 +22413,7 @@ components: preferred_name: type: string nullable: true + minLength: 1 maxLength: 30 base_wage_rate: type: number @@ -22069,6 +22427,7 @@ components: xero_user_id: type: string nullable: true + minLength: 1 maxLength: 255 date_left: type: string @@ -22281,8 +22640,10 @@ components: * `product_catalog` - Product Catalog location: type: string + nullable: true description: Where we are keeping this metal_type: + nullable: true description: |- Type of metal @@ -22294,11 +22655,10 @@ components: * `titanium` - Titanium * `zinc` - Zinc * `galvanized` - Galvanized - * `unspecified` - Unspecified * `other` - Other oneOf: - $ref: '#/components/schemas/MetalTypeEnum' - - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' alloy: type: string nullable: true @@ -22336,6 +22696,7 @@ components: item_code: type: string nullable: true + minLength: 1 description: Xero Item Code maxLength: 255 description: @@ -22384,8 +22745,11 @@ components: * `product_catalog` - Product Catalog location: type: string + nullable: true + minLength: 1 description: Where we are keeping this metal_type: + nullable: true description: |- Type of metal @@ -22397,19 +22761,20 @@ components: * `titanium` - Titanium * `zinc` - Zinc * `galvanized` - Galvanized - * `unspecified` - Unspecified * `other` - Other oneOf: - $ref: '#/components/schemas/MetalTypeEnum' - - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' alloy: type: string nullable: true + minLength: 1 description: Alloy specification (e.g., 304, 6061) maxLength: 50 specifics: type: string nullable: true + minLength: 1 description: Specific details (e.g., m8 countersunk socket screw) maxLength: 255 is_active: @@ -22597,6 +22962,7 @@ components: suburb: type: string nullable: true + minLength: 1 description: Suburb or neighbourhood (e.g., Thorndon, Northcote Point) maxLength: 100 city: @@ -22607,11 +22973,13 @@ components: state: type: string nullable: true + minLength: 1 description: State or province maxLength: 100 postal_code: type: string nullable: true + minLength: 1 description: Postal or ZIP code maxLength: 20 country: @@ -22622,6 +22990,7 @@ components: google_place_id: type: string nullable: true + minLength: 1 description: Google Place ID for this address maxLength: 255 latitude: @@ -22648,6 +23017,7 @@ components: notes: type: string nullable: true + minLength: 1 description: Additional notes (e.g., opening hours, access instructions) required: - city @@ -22940,6 +23310,7 @@ components: desc: type: string readOnly: true + nullable: true description: Description of this cost line quantity: type: number @@ -24259,6 +24630,7 @@ components: online_url: type: string format: uri + nullable: true description: Direct link to the document in Xero. messages: type: array @@ -24426,6 +24798,7 @@ components: - mode XeroPayItem: type: object + description: ModelSerializer that rejects "" wherever NULL is the column's unset. properties: id: type: string diff --git a/frontend/src/api/__tests__/client.resourceVersion.test.ts b/frontend/src/api/__tests__/client.resourceVersion.test.ts new file mode 100644 index 000000000..fec41196b --- /dev/null +++ b/frontend/src/api/__tests__/client.resourceVersion.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' +import { strongResourceVersion } from '@/api/client' + +describe('strongResourceVersion', () => { + it('prefers the strong resource-version header over a weak response ETag', () => { + expect( + strongResourceVersion({ + 'x-resource-version': '"job:123:strong"', + etag: 'W/"compressed"', + }), + ).toBe('"job:123:strong"') + }) + + it('falls back to a strong ETag', () => { + expect(strongResourceVersion({ etag: '"po:456:strong"' })).toBe('"po:456:strong"') + }) + + it('ignores a weak-only ETag', () => { + expect(strongResourceVersion({ etag: 'W/"compressed"' })).toBeNull() + }) +}) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 6d7ff16e7..606cb8689 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,5 +1,5 @@ import { Zodios } from '@zodios/core' -import axios from 'axios' +import axios, { type AxiosResponseHeaders, type RawAxiosResponseHeaders } from 'axios' import { endpoints } from './generated/api' import debug from 'debug' import { trimStringsDeep } from '../utils/sanitize' @@ -19,6 +19,23 @@ import { getSessionReplayId } from '@/services/sessionReplayState' const log = debug('api:client') +type ResourceVersionHeaders = AxiosResponseHeaders | RawAxiosResponseHeaders + +function strongHeaderValue(value: unknown): string | null { + if (typeof value !== 'string') { + return null + } + const normalized = value.trim() + if (!normalized || /^W\//i.test(normalized)) { + return null + } + return normalized +} + +export function strongResourceVersion(headers: ResourceVersionHeaders): string | null { + return strongHeaderValue(headers['x-resource-version']) ?? strongHeaderValue(headers.etag) ?? null +} + // Global registry for ETag management to avoid circular imports let etagManager: { getETag: (jobId: string) => string | null @@ -158,22 +175,22 @@ axios.interceptors.response.use( (response) => { // Capture ETag from job endpoint responses const url = response.config.url || '' - const etag = response.headers['etag'] + const resourceVersion = strongResourceVersion(response.headers) - if (etag && isJobEndpoint(url)) { + if (resourceVersion && isJobEndpoint(url)) { const jobId = extractJobId(url) if (jobId && etagManager) { - etagManager.setETag(jobId, etag) - log(`Captured ETag for ${url}:`, etag) + etagManager.setETag(jobId, resourceVersion) + log(`Captured resource version for ${url}:`, resourceVersion) } } // Capture ETag from PO endpoint responses - if (etag && isPoEndpoint(url)) { + if (resourceVersion && isPoEndpoint(url)) { const poId = extractPoId(url) if (poId && poEtagManager) { - poEtagManager.setETag(poId, etag) - log(`Captured ETag for ${url}:`, etag) + poEtagManager.setETag(poId, resourceVersion) + log(`Captured resource version for ${url}:`, resourceVersion) } } diff --git a/frontend/src/api/generated/api.ts b/frontend/src/api/generated/api.ts index 3a4c76662..c2274ada9 100644 --- a/frontend/src/api/generated/api.ts +++ b/frontend/src/api/generated/api.ts @@ -197,8 +197,9 @@ const PayrollReconciliationResponse = z.object({ heatmap: PayrollHeatmap, grand_totals: PayrollGrandTotals, }) +const RdtiTypeEnum = z.enum(['non_rd', 'core_rd', 'supporting_rd']) const RDTISpendCategorySummary = z.object({ - rdti_type: z.string(), + rdti_type: RdtiTypeEnum, label: z.string(), hours: z.number(), cost: z.number(), @@ -210,7 +211,7 @@ const RDTISpendJobDetail = z.object({ job_number: z.number().int(), job_name: z.string(), company_name: z.string(), - rdti_type: z.string(), + rdti_type: RdtiTypeEnum, hours: z.number(), cost: z.number(), revenue: z.number(), @@ -465,7 +466,6 @@ const Staff = z.object({ date_joined: z.string().datetime({ offset: true }), created_at: z.string().datetime({ offset: true }), updated_at: z.string().datetime({ offset: true }), - last_login: z.string().datetime({ offset: true }).nullable(), groups: z.array(z.number().int()).optional(), user_permissions: z.array(z.number().int()).optional(), icon_url: z.string().nullable(), @@ -474,9 +474,9 @@ const StaffCreateRequest = z.object({ email: z.string().min(1).max(254).email(), first_name: z.string().min(1).max(30), last_name: z.string().min(1).max(30), - preferred_name: z.string().max(30).nullish(), + preferred_name: z.string().min(1).max(30).nullish(), base_wage_rate: z.number().gt(-100000000).lt(100000000).optional(), - xero_user_id: z.string().max(255).nullish(), + xero_user_id: z.string().min(1).max(255).nullish(), date_left: z.string().nullish(), is_office_staff: z.boolean().optional(), is_workshop_staff: z.boolean().optional(), @@ -491,7 +491,6 @@ const StaffCreateRequest = z.object({ hours_sun: z.number().gt(-100).lt(100).optional(), date_joined: z.string().datetime({ offset: true }).optional(), created_at: z.string().datetime({ offset: true }).optional(), - last_login: z.string().datetime({ offset: true }).nullish(), groups: z.array(z.number().int()).optional(), user_permissions: z.array(z.number().int()).optional(), password: z.string().min(1).max(128), @@ -500,9 +499,9 @@ const StaffRequest = z.object({ email: z.string().min(1).max(254).email(), first_name: z.string().min(1).max(30), last_name: z.string().min(1).max(30), - preferred_name: z.string().max(30).nullish(), + preferred_name: z.string().min(1).max(30).nullish(), base_wage_rate: z.number().gt(-100000000).lt(100000000).optional(), - xero_user_id: z.string().max(255).nullish(), + xero_user_id: z.string().min(1).max(255).nullish(), date_left: z.string().nullish(), is_office_staff: z.boolean().optional(), is_workshop_staff: z.boolean().optional(), @@ -524,9 +523,9 @@ const PatchedStaffRequest = z email: z.string().min(1).max(254).email(), first_name: z.string().min(1).max(30), last_name: z.string().min(1).max(30), - preferred_name: z.string().max(30).nullable(), + preferred_name: z.string().min(1).max(30).nullable(), base_wage_rate: z.number().gt(-100000000).lt(100000000), - xero_user_id: z.string().max(255).nullable(), + xero_user_id: z.string().min(1).max(255).nullable(), date_left: z.string().nullable(), is_office_staff: z.boolean(), is_workshop_staff: z.boolean(), @@ -676,7 +675,7 @@ const CompanyPerson = z.object({ }) const CompanyPersonCreateRequest = z.object({ name: z.string().min(1).max(255), - email: z.string().email().nullish(), + email: z.string().min(1).email().nullish(), phone: z.string().nullish(), position: z.string().max(255).nullish(), notes: z.string().nullish(), @@ -730,7 +729,7 @@ const SupplierSearchAliasCreateRequest = z.object({ const CompanyUpdateRequest = z .object({ name: z.string().min(1).max(255), - email: z.string().email(), + email: z.string().min(1).email().nullable(), phone: z.string().nullable(), address: z.string(), is_account_customer: z.boolean(), @@ -745,7 +744,7 @@ const CompanyUpdateResponse = z.object({ const PatchedCompanyUpdateRequest = z .object({ name: z.string().min(1).max(255), - email: z.string().email(), + email: z.string().min(1).email().nullable(), phone: z.string().nullable(), address: z.string(), is_account_customer: z.boolean(), @@ -765,7 +764,7 @@ const ContactMethod = z.object({ method_type: ContactMethodTypeEnum, value: z.string().max(255), normalized_value: z.string(), - label: z.string().max(255).optional(), + label: z.string().max(255).nullish(), is_primary: z.boolean().optional().default(false), source: ContactMethodSourceEnum.optional(), created_at: z.string().datetime({ offset: true }), @@ -783,7 +782,7 @@ const ContactMethodRequest = z.object({ person: z.string().uuid().nullish(), method_type: ContactMethodTypeEnum, value: z.string().min(1).max(255), - label: z.string().max(255).optional(), + label: z.string().min(1).max(255).nullish(), is_primary: z.boolean().optional().default(false), source: ContactMethodSourceEnum.optional(), }) @@ -793,14 +792,14 @@ const PatchedContactMethodRequest = z person: z.string().uuid().nullable(), method_type: ContactMethodTypeEnum, value: z.string().min(1).max(255), - label: z.string().max(255), + label: z.string().min(1).max(255).nullable(), is_primary: z.boolean().default(false), source: ContactMethodSourceEnum, }) .partial() const CompanyCreateRequest = z.object({ name: z.string().min(1).max(255), - email: z.string().email().nullish(), + email: z.string().min(1).email().nullish(), phone: z.string().nullish(), address: z.string().nullish(), is_account_customer: z.boolean().optional().default(true), @@ -829,16 +828,6 @@ const CompanyDuplicateErrorResponse = z.object({ error: z.string(), existing_company: z.object({}).partial().passthrough(), }) -const JobPersonResponse = z.object({ - id: z.string().uuid(), - name: z.string(), - email: z.string().nullable(), -}) -const JobPersonUpdateRequest = z.object({ - id: z.string().uuid(), - name: z.string().min(1), - email: z.string().nullable(), -}) const SupplierPickupAddress = z.object({ id: z.string().uuid(), company: z.string().uuid(), @@ -863,32 +852,32 @@ const SupplierPickupAddressRequest = z.object({ company: z.string().uuid(), name: z.string().min(1).max(255), street: z.string().min(1).max(255), - suburb: z.string().max(100).nullish(), + suburb: z.string().min(1).max(100).nullish(), city: z.string().min(1).max(100), - state: z.string().max(100).nullish(), - postal_code: z.string().max(20).nullish(), + state: z.string().min(1).max(100).nullish(), + postal_code: z.string().min(1).max(20).nullish(), country: z.string().min(1).max(100).optional(), - google_place_id: z.string().max(255).nullish(), + google_place_id: z.string().min(1).max(255).nullish(), latitude: z.number().gt(-1000).lt(1000).nullish(), longitude: z.number().gt(-1000).lt(1000).nullish(), is_primary: z.boolean().optional(), - notes: z.string().nullish(), + notes: z.string().min(1).nullish(), }) const PatchedSupplierPickupAddressRequest = z .object({ company: z.string().uuid(), name: z.string().min(1).max(255), street: z.string().min(1).max(255), - suburb: z.string().max(100).nullable(), + suburb: z.string().min(1).max(100).nullable(), city: z.string().min(1).max(100), - state: z.string().max(100).nullable(), - postal_code: z.string().max(20).nullable(), + state: z.string().min(1).max(100).nullable(), + postal_code: z.string().min(1).max(20).nullable(), country: z.string().min(1).max(100), - google_place_id: z.string().max(255).nullable(), + google_place_id: z.string().min(1).max(255).nullable(), latitude: z.number().gt(-1000).lt(1000).nullable(), longitude: z.number().gt(-1000).lt(1000).nullable(), is_primary: z.boolean(), - notes: z.string().nullable(), + notes: z.string().min(1).nullable(), }) .partial() const CompanySearchResponse = z.object({ @@ -907,6 +896,7 @@ const CompanyDefaults = z.object({ company_acronym: z.string().max(10).nullish(), time_markup: z.number().gt(-1000).lt(1000).optional(), materials_markup: z.number().gt(-1000).lt(1000).optional(), + gst_rate: z.number().gte(0).lte(0.9999).optional(), wage_rate: z.number().gt(-10000).lt(10000).optional(), annual_leave_loading: z.number().gt(-1000).lt(1000).optional(), workshop_efficiency_factor: z.number().gt(-10).lt(10).optional(), @@ -970,9 +960,10 @@ const CompanyDefaultsRequest = z.object({ logo: z.instanceof(File).nullish(), logo_wide: z.instanceof(File).nullish(), xero_quote_terms: z.string().min(1).max(4000).optional(), - company_acronym: z.string().max(10).nullish(), + company_acronym: z.string().min(1).max(10).nullish(), time_markup: z.number().gt(-1000).lt(1000).optional(), materials_markup: z.number().gt(-1000).lt(1000).optional(), + gst_rate: z.number().gte(0).lte(0.9999).optional(), wage_rate: z.number().gt(-10000).lt(10000).optional(), annual_leave_loading: z.number().gt(-1000).lt(1000).optional(), workshop_efficiency_factor: z.number().gt(-10).lt(10).optional(), @@ -980,17 +971,17 @@ const CompanyDefaultsRequest = z.object({ starting_job_number: z.number().int().gte(-2147483648).lte(2147483647).optional(), starting_po_number: z.number().int().gte(-2147483648).lte(2147483647).optional(), po_prefix: z.string().min(1).max(10).optional(), - master_quote_template_url: z.string().max(200).url().nullish(), - master_quote_template_id: z.string().max(100).nullish(), - gdrive_quotes_folder_url: z.string().max(200).url().nullish(), - gdrive_quotes_folder_id: z.string().max(100).nullish(), - google_shared_drive_id: z.string().max(100).nullish(), - gdrive_how_we_work_folder_id: z.string().max(100).nullish(), - gdrive_sops_folder_id: z.string().max(100).nullish(), - gdrive_reference_library_folder_id: z.string().max(100).nullish(), + master_quote_template_url: z.string().min(1).max(200).url().nullish(), + master_quote_template_id: z.string().min(1).max(100).nullish(), + gdrive_quotes_folder_url: z.string().min(1).max(200).url().nullish(), + gdrive_quotes_folder_id: z.string().min(1).max(100).nullish(), + google_shared_drive_id: z.string().min(1).max(100).nullish(), + gdrive_how_we_work_folder_id: z.string().min(1).max(100).nullish(), + gdrive_sops_folder_id: z.string().min(1).max(100).nullish(), + gdrive_reference_library_folder_id: z.string().min(1).max(100).nullish(), accounting_provider: z.string().min(1).max(20).optional(), - xero_tenant_id: z.string().max(100).nullish(), - xero_shortcode: z.string().max(20).nullish(), + xero_tenant_id: z.string().min(1).max(100).nullish(), + xero_shortcode: z.string().min(1).max(20).nullish(), xero_sales_branding_theme_id: z.string().uuid().nullish(), enable_xero_sync: z.boolean().optional(), xero_automated_day_floor: z.number().int().gte(0).lte(2147483647).optional(), @@ -1011,15 +1002,15 @@ const CompanyDefaultsRequest = z.object({ fri_end: z.string().optional(), last_xero_sync: z.string().datetime({ offset: true }).nullish(), last_xero_deep_sync: z.string().datetime({ offset: true }).nullish(), - address_line1: z.string().max(255).nullish(), - address_line2: z.string().max(255).nullish(), - suburb: z.string().max(100).nullish(), - city: z.string().max(100).nullish(), - post_code: z.string().max(20).nullish(), + address_line1: z.string().min(1).max(255).nullish(), + address_line2: z.string().min(1).max(255).nullish(), + suburb: z.string().min(1).max(100).nullish(), + city: z.string().min(1).max(100).nullish(), + post_code: z.string().min(1).max(20).nullish(), country: z.string().min(1).max(100).optional(), - company_email: z.string().max(254).email().nullish(), - company_url: z.string().max(200).url().nullish(), - test_company_name: z.string().max(255).nullish(), + company_email: z.string().min(1).max(254).email().nullish(), + company_url: z.string().min(1).max(200).url().nullish(), + test_company_name: z.string().min(1).max(255).nullish(), kpi_daily_billable_hours_green: z.number().gt(-1000).lt(1000).optional(), kpi_daily_billable_hours_amber: z.number().gt(-1000).lt(1000).optional(), kpi_daily_gp_target: z.number().gt(-100000000).lt(100000000).optional(), @@ -1035,9 +1026,10 @@ const PatchedCompanyDefaultsRequest = z logo: z.instanceof(File).nullable(), logo_wide: z.instanceof(File).nullable(), xero_quote_terms: z.string().min(1).max(4000), - company_acronym: z.string().max(10).nullable(), + company_acronym: z.string().min(1).max(10).nullable(), time_markup: z.number().gt(-1000).lt(1000), materials_markup: z.number().gt(-1000).lt(1000), + gst_rate: z.number().gte(0).lte(0.9999), wage_rate: z.number().gt(-10000).lt(10000), annual_leave_loading: z.number().gt(-1000).lt(1000), workshop_efficiency_factor: z.number().gt(-10).lt(10), @@ -1045,17 +1037,17 @@ const PatchedCompanyDefaultsRequest = z starting_job_number: z.number().int().gte(-2147483648).lte(2147483647), starting_po_number: z.number().int().gte(-2147483648).lte(2147483647), po_prefix: z.string().min(1).max(10), - master_quote_template_url: z.string().max(200).url().nullable(), - master_quote_template_id: z.string().max(100).nullable(), - gdrive_quotes_folder_url: z.string().max(200).url().nullable(), - gdrive_quotes_folder_id: z.string().max(100).nullable(), - google_shared_drive_id: z.string().max(100).nullable(), - gdrive_how_we_work_folder_id: z.string().max(100).nullable(), - gdrive_sops_folder_id: z.string().max(100).nullable(), - gdrive_reference_library_folder_id: z.string().max(100).nullable(), + master_quote_template_url: z.string().min(1).max(200).url().nullable(), + master_quote_template_id: z.string().min(1).max(100).nullable(), + gdrive_quotes_folder_url: z.string().min(1).max(200).url().nullable(), + gdrive_quotes_folder_id: z.string().min(1).max(100).nullable(), + google_shared_drive_id: z.string().min(1).max(100).nullable(), + gdrive_how_we_work_folder_id: z.string().min(1).max(100).nullable(), + gdrive_sops_folder_id: z.string().min(1).max(100).nullable(), + gdrive_reference_library_folder_id: z.string().min(1).max(100).nullable(), accounting_provider: z.string().min(1).max(20), - xero_tenant_id: z.string().max(100).nullable(), - xero_shortcode: z.string().max(20).nullable(), + xero_tenant_id: z.string().min(1).max(100).nullable(), + xero_shortcode: z.string().min(1).max(20).nullable(), xero_sales_branding_theme_id: z.string().uuid().nullable(), enable_xero_sync: z.boolean(), xero_automated_day_floor: z.number().int().gte(0).lte(2147483647), @@ -1076,15 +1068,15 @@ const PatchedCompanyDefaultsRequest = z fri_end: z.string(), last_xero_sync: z.string().datetime({ offset: true }).nullable(), last_xero_deep_sync: z.string().datetime({ offset: true }).nullable(), - address_line1: z.string().max(255).nullable(), - address_line2: z.string().max(255).nullable(), - suburb: z.string().max(100).nullable(), - city: z.string().max(100).nullable(), - post_code: z.string().max(20).nullable(), + address_line1: z.string().min(1).max(255).nullable(), + address_line2: z.string().min(1).max(255).nullable(), + suburb: z.string().min(1).max(100).nullable(), + city: z.string().min(1).max(100).nullable(), + post_code: z.string().min(1).max(20).nullable(), country: z.string().min(1).max(100), - company_email: z.string().max(254).email().nullable(), - company_url: z.string().max(200).url().nullable(), - test_company_name: z.string().max(255).nullable(), + company_email: z.string().min(1).max(254).email().nullable(), + company_url: z.string().min(1).max(200).url().nullable(), + test_company_name: z.string().min(1).max(255).nullable(), kpi_daily_billable_hours_green: z.number().gt(-1000).lt(1000), kpi_daily_billable_hours_amber: z.number().gt(-1000).lt(1000), kpi_daily_gp_target: z.number().gt(-100000000).lt(100000000), @@ -1116,14 +1108,14 @@ const PhoneCallRecording = z.object({ id: z.string().uuid(), provider_recording_id: z.string(), account_code: z.string(), - filename: z.string(), - content_type: z.string(), + filename: z.string().nullable(), + content_type: z.string().nullable(), byte_size: z.number().int().nullable(), - sha256: z.string(), + sha256: z.string().nullable(), archived_at: z.string().datetime({ offset: true }).nullable(), - archive_error: z.string(), + archive_error: z.string().nullable(), provider_deleted_at: z.string().datetime({ offset: true }).nullable(), - provider_delete_error: z.string(), + provider_delete_error: z.string().nullable(), local_deleted_at: z.string().datetime({ offset: true }).nullable(), download_url: z.string().nullable(), created_at: z.string().datetime({ offset: true }), @@ -1137,14 +1129,14 @@ const PhoneCallRecord = z.object({ call_datetime: z.string().datetime({ offset: true }), call_date: z.string(), call_time: z.string(), - call_type: z.string(), - status: z.string(), - description: z.string(), - origin: z.string(), - destination: z.string(), + call_type: z.string().nullable(), + status: z.string().nullable(), + description: z.string().nullable(), + origin: z.string().nullable(), + destination: z.string().nullable(), direction: DirectionEnum, - our_number: z.string(), - external_number: z.string(), + our_number: z.string().nullable(), + external_number: z.string().nullable(), origin_endpoint: z.string().uuid().nullable(), origin_endpoint_label: z.string(), destination_endpoint: z.string().uuid().nullable(), @@ -1188,7 +1180,7 @@ const PhoneEndpoint = z.object({ endpoint_type: EndpointTypeEnum, staff: z.string().uuid().nullish(), staff_name: z.string(), - provider_account_code: z.string().max(100).optional(), + provider_account_code: z.string().max(100).nullish(), provider_metadata: z.unknown().optional(), is_active: z.boolean().optional(), created_at: z.string().datetime({ offset: true }), @@ -1199,7 +1191,7 @@ const PhoneEndpointRequest = z.object({ label: z.string().min(1).max(255), endpoint_type: EndpointTypeEnum, staff: z.string().uuid().nullish(), - provider_account_code: z.string().max(100).optional(), + provider_account_code: z.string().min(1).max(100).nullish(), provider_metadata: z.unknown().optional(), is_active: z.boolean().optional(), }) @@ -1209,7 +1201,7 @@ const PatchedPhoneEndpointRequest = z label: z.string().min(1).max(255), endpoint_type: EndpointTypeEnum, staff: z.string().uuid().nullable(), - provider_account_code: z.string().max(100), + provider_account_code: z.string().min(1).max(100).nullable(), provider_metadata: z.unknown(), is_active: z.boolean(), }) @@ -1221,7 +1213,7 @@ const PhoneProviderSettings = z.object({ base_url: z.string().max(200).url().nullish(), has_username: z.boolean(), has_password: z.boolean(), - account_code: z.string().max(100).optional(), + account_code: z.string().max(100).nullish(), created_at: z.string().datetime({ offset: true }), updated_at: z.string().datetime({ offset: true }), }) @@ -1229,15 +1221,16 @@ const PatchedPhoneProviderSettingsRequest = z .object({ downloads_enabled: z.boolean(), recording_deletion_enabled: z.boolean(), - base_url: z.string().max(200).url().nullable(), - username: z.string(), - password: z.string(), - account_code: z.string().max(100), + base_url: z.string().min(1).max(200).url().nullable(), + username: z.string().min(1), + password: z.string().min(1), + account_code: z.string().min(1).max(100).nullable(), }) .partial() const DataVersions = z.object({ stock: z.string(), kanban: z.string(), + kanban_related: z.string(), crm_calls: z.string(), }) const CompanyDefaultsJobDetail = z.object({ @@ -1249,7 +1242,7 @@ const CostLineKindEnum = z.enum(['time', 'material', 'adjust']) const PatchedCostLineCreateUpdateRequest = z .object({ kind: CostLineKindEnum, - desc: z.string().max(255), + desc: z.string().min(1).max(255).nullable(), quantity: z.number().gt(-10000000).lt(10000000), unit_cost: z.number().gt(-100000000).lt(100000000), unit_rev: z.number().gt(-100000000).lt(100000000), @@ -1264,7 +1257,7 @@ const PatchedCostLineCreateUpdateRequest = z const CostLine = z.object({ id: z.string().uuid(), kind: CostLineKindEnum, - desc: z.string().max(255).optional(), + desc: z.string().max(255).nullish(), quantity: z.number().gt(-10000000).lt(10000000).optional(), unit_cost: z.number().gt(-100000000).lt(100000000).optional(), unit_rev: z.number().gt(-100000000).lt(100000000).optional(), @@ -1511,7 +1504,11 @@ const JobCreateResponse = z.object({ job_number: z.number().int(), message: z.string(), }) -const JobRestErrorResponse = z.object({ error: z.string() }) +const AppErrorDetails = z.object({ error_id: z.string().uuid() }) +const JobRestErrorResponse = z.object({ + error: z.string(), + details: AppErrorDetails.optional(), +}) const CostSetKindEnum = z.enum(['estimate', 'quote', 'actual']) const CostSetSummary = z.object({ cost: z.number(), @@ -1532,7 +1529,7 @@ const JobFileStatusEnum = z.enum(['active', 'deleted']) const JobFile = z.object({ id: z.string().uuid(), filename: z.string().max(255), - mime_type: z.string().max(100).optional(), + mime_type: z.string().max(100).nullish(), uploaded_at: z.string().datetime({ offset: true }), status: JobFileStatusEnum.optional(), print_on_jobsheet: z.boolean().optional(), @@ -1586,8 +1583,6 @@ const XeroInvoice = z.object({ status: InvoiceStatusEnum.optional(), online_url: z.string().max(200).url().nullish(), }) -const RdtiTypeEnum = z.enum(['non_rd', 'core_rd', 'supporting_rd']) -const BlankEnum = z.unknown() const NullEnum = z.unknown() const Job = z.object({ id: z.string().uuid(), @@ -1623,7 +1618,7 @@ const Job = z.object({ xero_invoices: z.array(XeroInvoice), shop_job: z.boolean(), rejected_flag: z.boolean().optional(), - rdti_type: z.union([RdtiTypeEnum, BlankEnum, NullEnum]).nullish(), + rdti_type: z.union([RdtiTypeEnum, NullEnum]).nullish(), default_xero_pay_item_id: z.string().uuid().nullish(), default_xero_pay_item_name: z.string().nullable(), min_people: z.number().int().gte(-2147483648).lte(2147483647).optional(), @@ -1640,7 +1635,7 @@ const JobEvent = z.object({ delta_before: z.unknown().nullable(), delta_after: z.unknown().nullable(), delta_meta: z.unknown().nullable(), - delta_checksum: z.string(), + delta_checksum: z.string().nullable(), detail: z.unknown(), description: z.string(), can_undo: z.boolean(), @@ -1691,7 +1686,7 @@ const JobBasicInformationResponse = z.object({ }) const CostLineCreateUpdateRequest = z.object({ kind: CostLineKindEnum, - desc: z.string().max(255).optional(), + desc: z.string().min(1).max(255).nullish(), quantity: z.number().gt(-10000000).lt(10000000).optional(), unit_cost: z.number().gt(-100000000).lt(100000000).optional(), unit_rev: z.number().gt(-100000000).lt(100000000).optional(), @@ -1780,7 +1775,7 @@ const JobFileUploadPartialResponse = z.object({ const JobFileRequest = z.object({ id: z.string().uuid(), filename: z.string().min(1).max(255), - mime_type: z.string().max(100).optional(), + mime_type: z.string().min(1).max(100).nullish(), status: JobFileStatusEnum.optional(), print_on_jobsheet: z.boolean().optional(), }) @@ -1793,6 +1788,36 @@ const JobFileThumbnailErrorResponse = z.object({ status: z.string().optional().default('error'), message: z.string(), }) +const FinishJobSummary = z.object({ + job_value_excl_gst: z.number().gt(-10000000000).lt(10000000000), + valid_invoiced_excl_gst: z.number().gt(-10000000000).lt(10000000000), + outstanding_invoiced_incl_gst: z.number().gt(-10000000000).lt(10000000000), + remaining_to_invoice_excl_gst: z.number().gt(-10000000000).lt(10000000000), + remaining_gst: z.number().gt(-10000000000).lt(10000000000), + remaining_to_invoice_incl_gst: z.number().gt(-10000000000).lt(10000000000), + total_to_pay_incl_gst: z.number().gt(-10000000000).lt(10000000000), + over_invoiced_excl_gst: z.number().gt(-10000000000).lt(10000000000), +}) +const JobCompletionChecklist = z.object({ + foreman_signed_off: z.boolean(), + timesheets_collected: z.boolean(), + materials_checked: z.boolean(), + customer_called: z.boolean(), + released: z.boolean(), +}) +const JobFinishResponse = z.object({ + summary: FinishJobSummary, + checklist: JobCompletionChecklist, +}) +const PatchedJobCompletionChecklistUpdateRequest = z + .object({ + foreman_signed_off: z.boolean(), + timesheets_collected: z.boolean(), + materials_checked: z.boolean(), + customer_called: z.boolean(), + released: z.boolean(), + }) + .partial() const JobStatusEnum = z.enum([ 'draft', 'awaiting_approval', @@ -1826,7 +1851,7 @@ const JobHeaderResponse = z.object({ quote_acceptance_date: z.string().datetime({ offset: true }).nullish(), paid: z.boolean().optional(), rejected_flag: z.boolean().optional(), - rdti_type: z.union([RdtiTypeEnum, BlankEnum, NullEnum]).nullish(), + rdti_type: z.union([RdtiTypeEnum, NullEnum]).nullish(), min_people: z.number().int().gte(-2147483648).lte(2147483647).optional(), max_people: z.number().int().gte(-2147483648).lte(2147483647).optional(), is_urgent: z.boolean().optional(), @@ -1916,8 +1941,9 @@ const KanbanSuccessResponse = z.object({ message: z.string(), }) const KanbanErrorResponse = z.object({ - success: z.boolean().optional().default(false), error: z.string(), + details: AppErrorDetails.optional(), + success: z.boolean().optional().default(false), }) const CostSetSummaryOnly = z.object({ id: z.string(), @@ -1962,7 +1988,7 @@ const JobSummary = z.object({ xero_invoices: z.array(XeroInvoice), shop_job: z.boolean(), rejected_flag: z.boolean().optional(), - rdti_type: z.union([RdtiTypeEnum, BlankEnum, NullEnum]).nullish(), + rdti_type: z.union([RdtiTypeEnum, NullEnum]).nullish(), default_xero_pay_item_id: z.string().uuid().nullish(), default_xero_pay_item_name: z.string().nullable(), min_people: z.number().int().gte(-2147483648).lte(2147483647).optional(), @@ -2035,7 +2061,9 @@ const ApplyQuoteErrorResponse = z.object({ error: z.string(), }) const QuoteSyncErrorResponse = z.object({ error: z.string() }) -const LinkQuoteSheetRequest = z.object({ template_url: z.string().url() }).partial() +const LinkQuoteSheetRequest = z + .object({ template_url: z.string().min(1).url().nullable() }) + .partial() const LinkQuoteSheetResponse = z.object({ sheet_url: z.string().url(), sheet_id: z.string(), @@ -2184,6 +2212,13 @@ const FetchJobsResponse = z error: z.string(), }) .partial() +const KanbanChangesResponse = z.object({ + success: z.boolean(), + jobs: z.array(KanbanColumnJob), + removed_job_ids: z.array(z.string().uuid()), + full_refresh_required: z.boolean(), + error: z.string().optional(), +}) const JobStatusChoicesResponse = z.object({ statuses: z.object({}).partial().passthrough(), }) @@ -2336,7 +2371,7 @@ const JobProfitabilityReportResponse = z.object({ const TimesheetCostLine = z.object({ id: z.string().uuid(), kind: CostLineKindEnum, - desc: z.string(), + desc: z.string().nullable(), quantity: z.number().gt(-10000000).lt(10000000), unit_cost: z.number().gt(-100000000).lt(100000000), unit_rev: z.number().gt(-100000000).lt(100000000), @@ -2536,7 +2571,7 @@ const PersonDetail = z.object({ const PersonIdentityUpdateRequest = z .object({ name: z.string().min(1).max(255), - email: z.string().max(254).email().nullable(), + email: z.string().min(1).max(254).email().nullable(), }) .partial() const PersonIdentityUpdate = z @@ -2548,13 +2583,13 @@ const PersonIdentityUpdate = z const PatchedPersonIdentityUpdateRequest = z .object({ name: z.string().min(1).max(255), - email: z.string().max(254).email().nullable(), + email: z.string().min(1).max(254).email().nullable(), }) .partial() const CompanyLinkWriteRequest = z .object({ - position: z.string().max(255).nullable(), - notes: z.string().nullable(), + position: z.string().min(1).max(255).nullable(), + notes: z.string().min(1).nullable(), is_primary: z.boolean().default(false), }) .partial() @@ -2562,14 +2597,14 @@ const PersonContactMethodWriteRequest = z.object({ method_type: ContactMethodTypeEnum, value: z.string().min(1).max(255), is_primary: z.boolean().optional().default(false), - label: z.string().optional().default(''), + label: z.string().min(1).nullish(), }) const PatchedPersonContactMethodWriteRequest = z .object({ method_type: ContactMethodTypeEnum, value: z.string().min(1).max(255), is_primary: z.boolean().default(false), - label: z.string().default(''), + label: z.string().min(1).nullable(), }) .partial() const CategoriesResponse = z.object({ @@ -2637,7 +2672,7 @@ const PatchedFormEntryRequest = z const FormUpdateRequest = z .object({ title: z.string().min(1).max(255), - document_number: z.string().max(50).nullable(), + document_number: z.string().min(1).max(50).nullable(), tags: z.unknown(), form_schema: z.unknown(), status: FormStatusEnum, @@ -2646,7 +2681,7 @@ const FormUpdateRequest = z const PatchedFormUpdateRequest = z .object({ title: z.string().min(1).max(255), - document_number: z.string().max(50).nullable(), + document_number: z.string().min(1).max(50).nullable(), tags: z.unknown(), form_schema: z.unknown(), status: FormStatusEnum, @@ -2666,8 +2701,8 @@ const ProcedureList = z.object({ document_type: ProcedureDocumentTypeEnum, document_number: z.string().max(50).nullish(), title: z.string().max(255), - site_location: z.string().max(500).optional(), - google_doc_url: z.string().max(200).url().optional(), + site_location: z.string().max(500).nullish(), + google_doc_url: z.string().max(200).url().nullish(), job_number: z.string().nullable(), tags: z.unknown().optional(), status: ProcedureStatusEnum.optional(), @@ -2679,9 +2714,9 @@ const ProcedureDetail = z.object({ document_type: ProcedureDocumentTypeEnum, document_number: z.string().max(50).nullish(), title: z.string().max(255), - site_location: z.string().max(500).optional(), - google_doc_id: z.string(), - google_doc_url: z.string().url(), + site_location: z.string().max(500).nullish(), + google_doc_id: z.string().nullable(), + google_doc_url: z.string().url().nullable(), job_id: z.string().uuid().nullable(), job_number: z.string().nullable(), tags: z.unknown().optional(), @@ -2698,18 +2733,18 @@ const ProcedureCreateRequest = z.object({ const ProcedureUpdateRequest = z .object({ title: z.string().min(1).max(255), - document_number: z.string().max(50).nullable(), + document_number: z.string().min(1).max(50).nullable(), tags: z.unknown(), - site_location: z.string().max(500), + site_location: z.string().min(1).max(500).nullable(), status: ProcedureStatusEnum, }) .partial() const PatchedProcedureUpdateRequest = z .object({ title: z.string().min(1).max(255), - document_number: z.string().max(50).nullable(), + document_number: z.string().min(1).max(50).nullable(), tags: z.unknown(), - site_location: z.string().max(500), + site_location: z.string().min(1).max(500).nullable(), status: ProcedureStatusEnum, }) .partial() @@ -2803,6 +2838,11 @@ const DeliveryReceiptResponse = z.object({ success: z.boolean(), error: z.string().optional(), }) +const PurchaseOrderMutationErrorResponse = z.object({ + error: z.string(), + details: AppErrorDetails.optional(), + success: z.boolean().optional().default(false), +}) const PurchasingJobsResponse = z.object({ jobs: z.array(JobForPurchasing), total_count: z.number().int(), @@ -2917,7 +2957,6 @@ const MetalTypeEnum = z.enum([ 'titanium', 'zinc', 'galvanized', - 'unspecified', 'other', ]) const PurchaseOrderLine = z.object({ @@ -2930,7 +2969,7 @@ const PurchaseOrderLine = z.object({ supplier_item_code: z.string().max(50).nullish(), item_code: z.string().max(50).nullish(), received_quantity: z.number().gt(-100000000).lt(100000000).optional(), - metal_type: z.union([MetalTypeEnum, BlankEnum, NullEnum]).nullish(), + metal_type: z.union([MetalTypeEnum, NullEnum]).nullish(), alloy: z.string().max(50).nullish(), specifics: z.string().max(255).nullish(), location: z.string().max(255).nullish(), @@ -3102,8 +3141,8 @@ const StockItem = z.object({ unit_revenue: z.number().gt(-100000000).lt(100000000).nullish(), date: z.string().datetime({ offset: true }).optional(), source: StockItemSourceEnum, - location: z.string().optional(), - metal_type: z.union([MetalTypeEnum, BlankEnum]).optional(), + location: z.string().nullish(), + metal_type: z.union([MetalTypeEnum, NullEnum]).nullish(), alloy: z.string().max(50).nullish(), specifics: z.string().max(255).nullish(), is_active: z.boolean().optional(), @@ -3111,32 +3150,32 @@ const StockItem = z.object({ times_used: z.number().int(), }) const StockItemRequest = z.object({ - item_code: z.string().max(255).nullish(), + item_code: z.string().min(1).max(255).nullish(), description: z.string().min(1).max(255), quantity: z.number().gt(-100000000).lt(100000000), unit_cost: z.number().gt(-100000000).lt(100000000), unit_revenue: z.number().gt(-100000000).lt(100000000).nullish(), date: z.string().datetime({ offset: true }).optional(), source: StockItemSourceEnum, - location: z.string().optional(), - metal_type: z.union([MetalTypeEnum, BlankEnum]).optional(), - alloy: z.string().max(50).nullish(), - specifics: z.string().max(255).nullish(), + location: z.string().min(1).nullish(), + metal_type: z.union([MetalTypeEnum, NullEnum]).nullish(), + alloy: z.string().min(1).max(50).nullish(), + specifics: z.string().min(1).max(255).nullish(), is_active: z.boolean().optional(), }) const PatchedStockItemRequest = z .object({ - item_code: z.string().max(255).nullable(), + item_code: z.string().min(1).max(255).nullable(), description: z.string().min(1).max(255), quantity: z.number().gt(-100000000).lt(100000000), unit_cost: z.number().gt(-100000000).lt(100000000), unit_revenue: z.number().gt(-100000000).lt(100000000).nullable(), date: z.string().datetime({ offset: true }), source: StockItemSourceEnum, - location: z.string(), - metal_type: z.union([MetalTypeEnum, BlankEnum]), - alloy: z.string().max(50).nullable(), - specifics: z.string().max(255).nullable(), + location: z.string().min(1).nullable(), + metal_type: z.union([MetalTypeEnum, NullEnum]).nullable(), + alloy: z.string().min(1).max(50).nullable(), + specifics: z.string().min(1).max(255).nullable(), is_active: z.boolean(), }) .partial() @@ -3260,7 +3299,7 @@ const SessionReplayRecording = z.object({ initial_path: z.string(), latest_path: z.string(), job_id: z.string().uuid().nullable(), - user_agent: z.string(), + user_agent: z.string().nullable(), viewport_width: z.number().int().nullable(), viewport_height: z.number().int().nullable(), event_count: z.number().int(), @@ -3502,43 +3541,43 @@ const AIProvider = z.object({ id: z.number().int(), name: z.string().max(100), provider_type: ProviderTypeEnum, - model_name: z.string().max(100).optional(), + model_name: z.string().max(100).nullish(), default: z.boolean().optional(), }) const AIProviderCreateUpdateRequest = z.object({ name: z.string().min(1).max(100), provider_type: ProviderTypeEnum, - model_name: z.string().max(100).optional(), + model_name: z.string().min(1).max(100).nullish(), default: z.boolean().optional(), - api_key: z.string().optional(), + api_key: z.string().min(1).optional(), }) const AIProviderCreateUpdate = z.object({ name: z.string().max(100), provider_type: ProviderTypeEnum, - model_name: z.string().max(100).optional(), + model_name: z.string().max(100).nullish(), default: z.boolean().optional(), }) const PatchedAIProviderCreateUpdateRequest = z .object({ name: z.string().min(1).max(100), provider_type: ProviderTypeEnum, - model_name: z.string().max(100), + model_name: z.string().min(1).max(100).nullable(), default: z.boolean(), - api_key: z.string(), + api_key: z.string().min(1), }) .partial() const AIProviderRequest = z.object({ name: z.string().min(1).max(100), provider_type: ProviderTypeEnum, - model_name: z.string().max(100).optional(), + model_name: z.string().min(1).max(100).nullish(), default: z.boolean().optional(), }) const AppErrorRequest = z.object({ message: z.string().min(1), data: z.unknown().nullish(), - app: z.string().max(50).nullish(), - file: z.string().max(200).nullish(), - function: z.string().max(100).nullish(), + app: z.string().min(1).max(50).nullish(), + file: z.string().min(1).max(200).nullish(), + function: z.string().min(1).max(100).nullish(), severity: z.number().int().gte(-2147483648).lte(2147483647).optional(), job_id: z.string().uuid().nullish(), user_id: z.string().uuid().nullish(), @@ -3692,7 +3731,7 @@ const XeroInvoiceCreateRequest = z.object({ const XeroDocumentSuccessResponse = z.object({ success: z.boolean().optional().default(true), xero_id: z.string().uuid(), - online_url: z.string().url().optional(), + online_url: z.string().url().nullish(), messages: z.array(z.string()).optional(), company: z.string().optional(), total_excl_tax: z.number().gt(-10000000000).lt(10000000000).optional(), @@ -3741,6 +3780,7 @@ export const schemas = { PayrollHeatmap, PayrollGrandTotals, PayrollReconciliationResponse, + RdtiTypeEnum, RDTISpendCategorySummary, RDTISpendJobDetail, RDTISpendTotals, @@ -3822,8 +3862,6 @@ export const schemas = { CompanySearchResult, CompanyCreateResponse, CompanyDuplicateErrorResponse, - JobPersonResponse, - JobPersonUpdateRequest, SupplierPickupAddress, SupplierPickupAddressRequest, PatchedSupplierPickupAddressRequest, @@ -3887,6 +3925,7 @@ export const schemas = { ArchiveJobsResponse, JobCreateRequest, JobCreateResponse, + AppErrorDetails, JobRestErrorResponse, CostSetKindEnum, CostSetSummary, @@ -3902,8 +3941,6 @@ export const schemas = { Invoice, XeroQuote, XeroInvoice, - RdtiTypeEnum, - BlankEnum, NullEnum, Job, JobEvent, @@ -3932,6 +3969,10 @@ export const schemas = { JobFileRequest, JobFileUpdateSuccessResponse, JobFileThumbnailErrorResponse, + FinishJobSummary, + JobCompletionChecklist, + JobFinishResponse, + PatchedJobCompletionChecklistUpdateRequest, JobStatusEnum, JobHeaderResponse, JobInvoicesResponse, @@ -3984,6 +4025,7 @@ export const schemas = { KanbanColumnJob, FetchJobsByColumnResponse, FetchJobsResponse, + KanbanChangesResponse, JobStatusChoicesResponse, FetchStatusValuesResponse, WeeklyMetrics, @@ -4070,6 +4112,7 @@ export const schemas = { DeliveryReceiptLineRequest, DeliveryReceiptRequest, DeliveryReceiptResponse, + PurchaseOrderMutationErrorResponse, PurchasingJobsResponse, ProductMapping, ProductMappingListResponse, @@ -5457,65 +5500,6 @@ Endpoint: /api/app-errors/<id>/`, }, ], }, - { - method: 'get', - path: '/api/companies/jobs/:job_id/person/', - alias: 'companies_jobs_person_retrieve', - description: `Retrieve person information for a specific job.`, - requestFormat: 'json', - parameters: [ - { - name: 'job_id', - type: 'Path', - schema: z.string().uuid(), - }, - ], - response: JobPersonResponse, - errors: [ - { - status: 404, - schema: CompanyErrorResponse, - }, - { - status: 500, - schema: CompanyErrorResponse, - }, - ], - }, - { - method: 'put', - path: '/api/companies/jobs/:job_id/person/', - alias: 'companies_jobs_person_update', - description: `Update the person associated with a specific job.`, - requestFormat: 'json', - parameters: [ - { - name: 'body', - type: 'Body', - schema: JobPersonUpdateRequest, - }, - { - name: 'job_id', - type: 'Path', - schema: z.string().uuid(), - }, - ], - response: JobPersonResponse, - errors: [ - { - status: 400, - schema: CompanyErrorResponse, - }, - { - status: 404, - schema: CompanyErrorResponse, - }, - { - status: 500, - schema: CompanyErrorResponse, - }, - ], - }, { method: 'get', path: '/api/companies/pickup-addresses/', @@ -6438,7 +6422,7 @@ POST /job/rest/cost_lines/<cost_line_id>/approve`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6512,7 +6496,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/link/`, { name: 'body', type: 'Body', - schema: z.object({ template_url: z.string().url() }).partial(), + schema: z.object({ template_url: z.string().min(1).url().nullable() }).partial(), }, { name: 'id', @@ -6604,7 +6588,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6630,7 +6614,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6651,7 +6635,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6672,7 +6656,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6801,7 +6785,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6847,7 +6831,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6868,7 +6852,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -6894,15 +6878,15 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, { status: 409, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, { status: 429, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7085,6 +7069,53 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, }, ], }, + { + method: 'get', + path: '/api/job/jobs/:job_id/finish/', + alias: 'job_jobs_finish_retrieve', + description: `Fetch the authoritative Finish Job customer balance and completion checklist for a job. All currency values are calculated server-side.`, + requestFormat: 'json', + parameters: [ + { + name: 'job_id', + type: 'Path', + schema: z.string().uuid(), + }, + ], + response: JobFinishResponse, + errors: [ + { + status: 400, + schema: JobRestErrorResponse, + }, + ], + }, + { + method: 'patch', + path: '/api/job/jobs/:job_id/finish/', + alias: 'job_jobs_finish_partial_update', + description: `Update one or more completion checklist items. Each changed item adds a job-history event. Unknown item keys are rejected.`, + requestFormat: 'json', + parameters: [ + { + name: 'body', + type: 'Body', + schema: PatchedJobCompletionChecklistUpdateRequest, + }, + { + name: 'job_id', + type: 'Path', + schema: z.string().uuid(), + }, + ], + response: JobFinishResponse, + errors: [ + { + status: 400, + schema: JobRestErrorResponse, + }, + ], + }, { method: 'get', path: '/api/job/jobs/:job_id/header/', @@ -7102,7 +7133,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7123,7 +7154,7 @@ POST /job/rest/jobs/<uuid:pk>/quote/preview/`, errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7325,7 +7356,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7351,7 +7382,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7421,7 +7452,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7442,7 +7473,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7468,7 +7499,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7633,7 +7664,7 @@ Expected JSON: errors: [ { status: 400, - schema: z.object({ error: z.string() }), + schema: JobRestErrorResponse, }, ], }, @@ -7742,6 +7773,27 @@ Expected JSON: ], response: FetchJobsResponse, }, + { + method: 'get', + path: '/api/job/jobs/kanban-changes/', + alias: 'getKanbanChanges', + description: `Return changed Kanban cards after an opaque Job dataset version.`, + requestFormat: 'json', + parameters: [ + { + name: 'after', + type: 'Query', + schema: z.string(), + }, + ], + response: KanbanChangesResponse, + errors: [ + { + status: 400, + schema: KanbanErrorResponse, + }, + ], + }, { method: 'get', path: '/api/job/jobs/status-choices/', @@ -9126,7 +9178,19 @@ Concurrency is controlled in this endpoint (ETag/If-Match).`, errors: [ { status: 400, - schema: DeliveryReceiptResponse, + schema: PurchaseOrderMutationErrorResponse, + }, + { + status: 404, + schema: PurchaseOrderMutationErrorResponse, + }, + { + status: 412, + schema: PurchaseOrderMutationErrorResponse, + }, + { + status: 500, + schema: PurchaseOrderMutationErrorResponse, }, ], }, diff --git a/frontend/src/components/CreateCompanyModal.vue b/frontend/src/components/CreateCompanyModal.vue index 8660db70b..33e78f395 100644 --- a/frontend/src/components/CreateCompanyModal.vue +++ b/frontend/src/components/CreateCompanyModal.vue @@ -427,8 +427,7 @@ const handleDuplicateCompanyError = (error: unknown): boolean => { } const existingCompany = parsedDuplicate.data.existing_company as - | Record - | undefined + Record | undefined const nameValue = typeof existingCompany?.name === 'string' ? existingCompany.name : 'Existing company' const xeroIdValue = diff --git a/frontend/src/components/PersonSelector.vue b/frontend/src/components/PersonSelector.vue index 497252d6f..df525c534 100644 --- a/frontend/src/components/PersonSelector.vue +++ b/frontend/src/components/PersonSelector.vue @@ -94,12 +94,14 @@ const props = withDefaults( companyName: string modelValue?: string initialPersonId?: string + autoSelectPrimary?: boolean }>(), { placeholder: 'No person selected', optional: true, modelValue: '', initialPersonId: '', + autoSelectPrimary: true, }, ) @@ -339,8 +341,8 @@ watch( ) watch( - () => [props.companyId, props.initialPersonId], - async ([companyId, initialId]) => { + () => [props.companyId, props.initialPersonId, props.autoSelectPrimary] as const, + async ([companyId, initialId, autoSelectPrimary]) => { log('unified watch:', { companyId, initialId }) // Clear local selection without emitting when the company changes. @@ -364,14 +366,14 @@ watch( // Selection priority: // 1) If initialId provided → select that person - // 2) Else if primary person exists → select primary + // 2) Else if enabled and a primary person exists → select primary // 3) Else → clear selection // NOTE: suppressEmit=true prevents API calls during initialization/company change // (auto-selection should not trigger saves - only user actions should) const decideAndSelect = () => { const choose = (initialId && people.value.find((person) => person.person_id === initialId)) || - (!initialId && findPrimaryPerson()) || + (!initialId && autoSelectPrimary && findPrimaryPerson()) || null suppressEmit.value = true diff --git a/frontend/src/components/SectionForm.vue b/frontend/src/components/SectionForm.vue index fb38f2832..1a1d32e1d 100644 --- a/frontend/src/components/SectionForm.vue +++ b/frontend/src/components/SectionForm.vue @@ -712,6 +712,10 @@ watch( if (field.type === 'url' && normalized[field.key]) { normalized[field.key] = normalizeUrl(normalized[field.key] as string) } + // A cleared box means unset, which these fields store as null. + if (normalized[field.key] === '') { + normalized[field.key] = null + } } emit('update:modelValue', normalized) }, diff --git a/frontend/src/components/StaffFormModal.vue b/frontend/src/components/StaffFormModal.vue index 89fa11562..25c6e54f0 100644 --- a/frontend/src/components/StaffFormModal.vue +++ b/frontend/src/components/StaffFormModal.vue @@ -406,8 +406,6 @@ const form = ref({ is_superuser: false, groups: '', user_permissions: '', - last_login: '', - date_joined: '', date_left: '', }) const error = ref('') @@ -465,8 +463,6 @@ watch( Array.isArray(staff.user_permissions) && staff.user_permissions.length > 0 ? staff.user_permissions.join(', ') : '', - last_login: staff.last_login || '', - date_joined: staff.date_joined || '', date_left: staff.date_left || '', } } else { @@ -491,8 +487,6 @@ watch( is_superuser: false, groups: '', user_permissions: '', - last_login: '', - date_joined: '', date_left: '', } } @@ -518,8 +512,6 @@ async function submitForm() { // password and its confirmation, which must never reach the browser console. // Prepare base data - shared between validation and API call - const lastLogin = normalizeOptionalString(form.value.last_login) - const dateJoined = normalizeOptionalString(form.value.date_joined) const preferredName = normalizeOptionalString(form.value.preferred_name) const xeroUserId = normalizeOptionalString(form.value.xero_user_id) // date_left is always sent (null when blank) so an offboarded staff member @@ -561,8 +553,6 @@ async function submitForm() { // Handle optional fields - only include if they have a value ...(preferredName && { preferred_name: preferredName }), ...(xeroUserId && { xero_user_id: xeroUserId }), - ...(lastLogin && { last_login: lastLogin }), - ...(dateJoined && { date_joined: dateJoined }), } // Add password if provided diff --git a/frontend/src/components/TaskExecutionsModal.vue b/frontend/src/components/TaskExecutionsModal.vue index 01f2667ce..f9a7993ee 100644 --- a/frontend/src/components/TaskExecutionsModal.vue +++ b/frontend/src/components/TaskExecutionsModal.vue @@ -73,8 +73,7 @@
      Traceback
      {{ exec.traceback }}
      + >{{ exec.traceback }}
      diff --git a/frontend/src/components/admin/AIProviderFormModal.vue b/frontend/src/components/admin/AIProviderFormModal.vue index a145caeaa..2e479c498 100644 --- a/frontend/src/components/admin/AIProviderFormModal.vue +++ b/frontend/src/components/admin/AIProviderFormModal.vue @@ -161,7 +161,12 @@ const [api_key] = defineField('api_key') const [is_default] = defineField('default') const onSubmit = handleSubmit((values) => { - const dataToSave = { ...values } + const dataToSave = { + ...values, + // A cleared box means unset, which this column stores as null, not "". + // Omitting the key instead would make an existing model name unclearable. + model_name: values.model_name || null, + } if (isEditing.value && !dataToSave.api_key) { delete dataToSave.api_key diff --git a/frontend/src/components/admin/__tests__/AIProviderFormModal.test.ts b/frontend/src/components/admin/__tests__/AIProviderFormModal.test.ts new file mode 100644 index 000000000..876e6e730 --- /dev/null +++ b/frontend/src/components/admin/__tests__/AIProviderFormModal.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi } from 'vitest' +import { mount, flushPromises } from '@vue/test-utils' + +import AIProviderFormModal from '../AIProviderFormModal.vue' + +// The modal's Dialog and Select primitives (reka-ui) render via Teleport, +// which is irrelevant to the submit payload under test; stub them to plain +// pass-through markup so the form inputs are queryable directly. +const stubs = { + Dialog: { template: '
      ' }, + DialogContent: { template: '
      ' }, + DialogHeader: { template: '
      ' }, + DialogTitle: { template: '
      ' }, + DialogDescription: { template: '
      ' }, + DialogFooter: { template: '
      ' }, + Select: { template: '
      ' }, + SelectTrigger: { template: '
      ' }, + SelectContent: { template: '
      ' }, + SelectItem: { template: '
      ' }, + SelectValue: { template: '
      ' }, + Switch: { template: '' }, + Button: { template: '' }, +} + +const existingProvider = { + id: 7, + name: 'Gemini prod', + provider_type: 'Gemini' as const, + model_name: 'gemini-flash-latest', + default: true, +} + +// vee-validate resolves the validation schema across macrotasks, so a +// microtask flush alone lands before the submit handler runs. Poll for the +// emit rather than guessing a delay. +async function submitAndGetSaved(wrapper: ReturnType) { + await wrapper.find('form').trigger('submit') + await vi.waitUntil(() => wrapper.emitted('save')) + await flushPromises() + return wrapper.emitted('save')?.at(-1)?.[0] as Record +} + +describe('AIProviderFormModal model_name', () => { + it('emits null when an existing model name is cleared, so it can be unset', async () => { + const wrapper = mount(AIProviderFormModal, { + props: { provider: existingProvider }, + global: { stubs }, + }) + + await wrapper.find('#model_name').setValue('') + + // Omitting the key would leave the stored name in place: PATCH only + // writes fields it is sent. + expect(await submitAndGetSaved(wrapper)).toHaveProperty('model_name', null) + }) + + it('emits null rather than "" when creating a provider with no model name', async () => { + const wrapper = mount(AIProviderFormModal, { + props: { provider: null }, + global: { stubs }, + }) + + await wrapper.find('#name').setValue('New provider') + await wrapper.find('#api_key').setValue('sk-test') + + // The column's unset is NULL and the serializer rejects "" outright. + expect(await submitAndGetSaved(wrapper)).toHaveProperty('model_name', null) + }) + + it('emits the entered model name unchanged', async () => { + const wrapper = mount(AIProviderFormModal, { + props: { provider: existingProvider }, + global: { stubs }, + }) + + await wrapper.find('#model_name').setValue('gemini-pro-latest') + + expect(await submitAndGetSaved(wrapper)).toHaveProperty('model_name', 'gemini-pro-latest') + }) +}) diff --git a/frontend/src/components/admin/errors/ErrorDialog.vue b/frontend/src/components/admin/errors/ErrorDialog.vue index 858260557..0ee7df41f 100644 --- a/frontend/src/components/admin/errors/ErrorDialog.vue +++ b/frontend/src/components/admin/errors/ErrorDialog.vue @@ -131,8 +131,7 @@ const errorTypeLabel = computed(() => {
      {{ rawPayload }}
      + >{{ rawPayload }}
    @@ -99,7 +89,7 @@ - + @@ -268,174 +128,6 @@ - - - - - - Create Invoice - - Choose how to invoice - {{ - props.pricingMethodology === 'fixed_price' - ? 'this quoted job' - : 'time & materials' - }} - - -
    - -
    -
    - Quote total - {{ formatCurrency(quoteTotal) }} -
    -
    - Actual revenue to date - {{ - formatCurrency(actualSummary.rev) - }} -
    -
    - Already invoiced - {{ formatCurrency(invoiceTotal) }} -
    -
    - Current amount to invoice - {{ - formatCurrency(toBeInvoiced) - }} -
    -
    - - -
    - - - - - -
    -
    - - - -
    -
    @@ -444,7 +136,7 @@ import debug from 'debug' const log = debug('job:actual') import { toLocalDateString } from '../../utils/dateUtils' -import { formatCurrency, formatDate } from '@/utils/string-formatting' +import { formatCurrency } from '@/utils/string-formatting' import { normalizeOptionalDecimal } from '@/utils/number' import { onMounted, ref, computed, reactive } from 'vue' import { useRouter } from 'vue-router' @@ -457,10 +149,8 @@ import { schemas } from '../../api/generated/api' import { useSmartCostLineDelete } from '../../composables/useSmartCostLineDelete' import { useCostSummary } from '../../composables/useCostSummary' import { useCostLineDrafts } from '@/composables/useCostLineDrafts' -import { useXeroConnection } from '../../composables/useXeroConnection' import { api } from '../../api/client' import { z } from 'zod' -import type { AxiosError } from 'axios' import type { KindOption } from '../shared/SmartCostLinesTable.vue' import { useStockStore } from '../../stores/stockStore' import SmartCostLinesTable from '../shared/SmartCostLinesTable.vue' @@ -473,18 +163,12 @@ import { DialogFooter, } from '../ui/dialog' import { Button } from '../ui/button' -import { Card, CardHeader, CardFooter, CardContent, CardDescription, CardTitle } from '../ui/card' -import { Input } from '../ui/input' -import { ExternalLink, Trash2 } from 'lucide-vue-next' -import { Badge } from '../ui/badge' import { useSaveFeedback } from '@/composables/useSaveFeedback' type CostLine = z.infer type CostSet = z.infer type KanbanStaff = z.infer type StockConsumeRequest = z.infer -type Invoice = z.infer -type XeroInvoiceCreateRequest = z.infer // Type guard functions to safely access meta and ext_refs function isDeliveryReceiptMeta(meta: unknown): meta is { source: string; po_number?: string } { @@ -526,11 +210,7 @@ function isStockExtRefs(extRefs: unknown): extRefs is { stock_id: string } { const props = defineProps<{ jobId: string - jobNumber: string pricingMethodology: string - quoted: boolean - fullyInvoiced: boolean - paid?: boolean }>() const jobActualSaveFeedback = useSaveFeedback(`job-actual:${props.jobId}`, { toastErrors: false, @@ -538,162 +218,17 @@ const jobActualSaveFeedback = useSaveFeedback(`job-actual:${props.jobId}`, { const emit = defineEmits<{ 'cost-line-changed': [] - 'quote-created': [] - 'quote-accepted': [] - 'invoice-created': [] - 'quote-deleted': [] - 'invoice-deleted': [] }>() const stockStore = useStockStore() -// Local state for invoices -const isCreatingInvoice = ref(false) -const deletingInvoiceId = ref(null) // Track which invoice is being deleted -const { xeroConnected } = useXeroConnection() - -// Local state for KPIs +// KPI chips. Invoice figures deliberately live in Finish Job only, so there is +// one authoritative place showing what the customer owes. const estimateTotal = ref(0) const quoteTotal = ref(0) const costsSummaryLoading = ref(false) - -// --- COMPUTED PROPERTIES --- -const invoices = ref>([]) const timeAndExpenses = computed(() => actualSummary.value.rev) -const invoiceTotal = computed(() => { - if (!invoices.value.length) return 0 - return invoices.value - .filter((inv) => inv.status !== 'VOIDED' && inv.status !== 'DELETED') - .reduce((sum, inv) => sum + (inv.total_excl_tax || 0), 0) -}) - -const toBeInvoiced = computed(() => { - // For fixed price jobs with a quote, use the quoted amount - // For T&M jobs or jobs without quotes, use the actual time & expenses - const amountToInvoice = - props.pricingMethodology === 'fixed_price' && quoteTotal.value > 0 - ? quoteTotal.value - : actualSummary.value.rev - - return amountToInvoice - invoiceTotal.value -}) - -const invoiceButtonText = computed(() => { - if (!xeroConnected.value) { - return 'Login to Xero first' - } - return 'Create Invoice' -}) - -// --- INVOICE MODAL --- -const showInvoiceModal = ref(false) -const selectedInvoiceMode = ref(null) -const invoicePercentInput = ref('') -const invoiceAmountInput = ref('') - -const createInvoice = () => { - selectedInvoiceMode.value = null - invoicePercentInput.value = '' - invoiceAmountInput.value = '' - showInvoiceModal.value = true -} - -function selectInvoiceMode(mode: string) { - selectedInvoiceMode.value = mode - invoicePercentInput.value = '' - invoiceAmountInput.value = '' -} - -async function executeCreateInvoice(mode: string) { - if (!props.jobId || isCreatingInvoice.value) return - - let percent: number | undefined - let amount: number | undefined - - if (mode === 'invoice_percent') { - const pct = parseFloat(invoicePercentInput.value) - if (isNaN(pct) || pct <= 0 || pct > 100) { - toast.error('Please enter a valid percentage (1-100)') - return - } - percent = pct - } - - if (mode === 'invoice_amount') { - const amt = parseFloat(invoiceAmountInput.value) - if (isNaN(amt) || amt <= 0) { - toast.error('Please enter a valid amount') - return - } - amount = amt - } - - showInvoiceModal.value = false - isCreatingInvoice.value = true - - try { - const body = { mode } as XeroInvoiceCreateRequest - if (percent !== undefined) body.percent = percent - if (amount !== undefined) body.amount = amount - - const response = await api.xero_create_invoice_create(body, { - params: { job_id: props.jobId }, - }) - if (!response?.success) { - const msgs = response?.messages?.length ? response.messages : ['Failed to create invoice'] - msgs.forEach((msg: string) => toast.error(msg)) - return - } - toast.success('Invoice created successfully!') - if (response.messages?.length) { - response.messages.forEach((msg: string) => toast.warning(msg)) - } - emit('invoice-created') - await loadInvoices() - } catch (err: unknown) { - let msg = 'Unexpected error while trying to create invoice.' - log('Error creating invoice:', err) - if ((err as AxiosError).isAxiosError) { - const axiosErr = err as AxiosError<{ message: string }> - const errorData = axiosErr.response?.data - if (typeof errorData === 'object' && errorData !== null && 'error' in errorData) { - msg = String((errorData as { error: string }).error) - } - } - toast.error(`Failed to create invoice: ${msg}`) - } finally { - isCreatingInvoice.value = false - } -} - -const goToInvoiceOnXero = (url: string | null | undefined) => { - if (url && url !== '#') { - window.open(url, '_blank') - } else { - toast.error('No online URL available for this invoice.') - } -} - -const deleteInvoiceOnXero = async (invoiceXeroId: string) => { - if (!props.jobId || deletingInvoiceId.value) return - deletingInvoiceId.value = invoiceXeroId - try { - await api.xero_delete_invoice_destroy(undefined, { - params: { job_id: props.jobId }, - queries: { xero_invoice_id: invoiceXeroId }, - }) - toast.success('Invoice deleted successfully!') - emit('invoice-deleted') - await loadInvoices() // Reload invoices after deletion - } catch (err) { - console.error('Error deleting invoice:', err) - toast.error('Failed to delete invoice.') - } finally { - deletingInvoiceId.value = null - } -} - const router = useRouter() const costLines = ref([]) @@ -777,18 +312,6 @@ async function loadCostsSummary() { } } -async function loadInvoices() { - try { - const response = await api.job_jobs_invoices_retrieve({ - params: { job_id: props.jobId }, - }) - // Zodios returns data directly, not wrapped in {success, data} - invoices.value = response.invoices || [] - } catch (error) { - log('Failed to load invoices:', error) - } -} - // Use the smart delete composable const { handleSmartDelete } = useSmartCostLineDelete({ costLines, @@ -899,7 +422,7 @@ async function handleCreateLine(line: CostLine): Promise { const costLineDraftSession = useCostLineDrafts({ costLines, createLine: handleCreateLine }) onMounted(async () => { - await Promise.all([loadStaff(), loadActualCosts(), loadCostsSummary(), loadInvoices()]) + await Promise.all([loadStaff(), loadActualCosts(), loadCostsSummary()]) }) // Use the cost summary composable (simple version for actual) diff --git a/frontend/src/components/job/JobCostAnalysisTab.vue b/frontend/src/components/job/JobFinishTab.vue similarity index 51% rename from frontend/src/components/job/JobCostAnalysisTab.vue rename to frontend/src/components/job/JobFinishTab.vue index f8270976c..6dcd10913 100644 --- a/frontend/src/components/job/JobCostAnalysisTab.vue +++ b/frontend/src/components/job/JobFinishTab.vue @@ -2,7 +2,7 @@
    -

    Cost Analysis

    +

    Finish Job

    Compare Estimate and Actual cost sets for this Time & Materials job @@ -22,11 +22,174 @@

    -

    Loading cost data...

    +

    Loading job financials...

    -
    +
    +

    Could not load this job's financials.

    +

    + Reload the page. Do not quote the customer a figure until it loads. +

    +
    + + + +
    +
    +
    Labour budget
    +
    + {{ formatNumber(labourBudgetHours) }} +
    +
    +
    +
    Hours used
    +
    + {{ formatNumber(actual.hours) }} +
    +
    +
    +
    + {{ labourOverrunHours > 0 ? 'Overrun' : 'Hours remaining' }} +
    +
    + {{ formatNumber(labourOverrunHours > 0 ? labourOverrunHours : labourRemainingHours) }} +
    +
    +
    + +
    @@ -157,6 +320,8 @@ import { computed, ref, onMounted, reactive } from 'vue' import { toast } from 'vue-sonner' import { ArrowUp, ArrowDown, CheckCircle, AlertTriangle, XCircle } from 'lucide-vue-next' import { api } from '../../api/client' +import debug from 'debug' +import JobInvoiceCard from './JobInvoiceCard.vue' import { formatCurrency } from '@/utils/string-formatting' import { schemas } from '../../api/generated/api' import { z } from 'zod' @@ -177,6 +342,8 @@ type JobCostSummaryResponse = z.infer const props = defineProps<{ jobId: string pricingMethodology: string + jobStatus?: string + paid?: boolean }>() const loading = ref(true) @@ -189,6 +356,39 @@ const zeroSummary = (): NormalizedCostSummary => ({ profitMargin: 0, }) +const log = debug('job:finish') + +type FinishJobSummary = z.infer +type JobCompletionChecklist = z.infer +type ChecklistItemKey = keyof JobCompletionChecklist + +// No fabricated placeholder: until the server answers there is no balance, and a +// failed load must not render as a settled $0 job. +const summary = ref(null) +const loadError = ref(false) +const checklist = reactive({ + foreman_signed_off: false, + timesheets_collected: false, + materials_checked: false, + customer_called: false, + released: false, +}) +const savingChecklistKey = ref(null) + +const checklistItems: Array<{ key: ChecklistItemKey; label: string }> = [ + { key: 'foreman_signed_off', label: 'Has the foreman signed off the job?' }, + { key: 'timesheets_collected', label: 'Have you collected the timesheet entries?' }, + { key: 'materials_checked', label: 'Have you checked the materials on the job?' }, + { key: 'customer_called', label: 'Have you called the customer?' }, + { key: 'released', label: 'Has the job been released?' }, +] + +const jobValueLabel = computed(() => + props.pricingMethodology === 'fixed_price' + ? 'Quote total (excl GST)' + : 'Job value to date (excl GST)', +) + const estimate = reactive(zeroSummary()) const quote = reactive(zeroSummary()) const actual = reactive(zeroSummary()) @@ -200,26 +400,37 @@ const ensureSummary = (s?: JobCostSetSummaryOutput | null): NormalizedCostSummar profitMargin: s?.profitMargin ?? 0, }) +async function loadFinishSummary() { + const response = await api.job_jobs_finish_retrieve({ params: { job_id: props.jobId } }) + summary.value = response.summary + Object.assign(checklist, response.checklist) +} + +async function loadCostSummary() { + const resp: JobCostSummaryResponse = await api.job_jobs_costs_summary_retrieve({ + params: { job_id: props.jobId }, + }) + + Object.assign(estimate, ensureSummary(resp.estimate)) + Object.assign(quote, ensureSummary(resp.quote)) + Object.assign(actual, ensureSummary(resp.actual)) + + hasValidQuoteData.value = + !!resp.quote && ((resp.quote.cost ?? 0) > 0 || (resp.quote.rev ?? 0) > 0) +} + async function loadAll() { loading.value = true + loadError.value = false try { - const resp: JobCostSummaryResponse = await api.job_jobs_costs_summary_retrieve({ - params: { job_id: props.jobId }, - }) - - Object.assign(estimate, ensureSummary(resp.estimate)) - Object.assign(quote, ensureSummary(resp.quote)) - Object.assign(actual, ensureSummary(resp.actual)) - - hasValidQuoteData.value = - !!resp.quote && ((resp.quote.cost ?? 0) > 0 || (resp.quote.rev ?? 0) > 0) + await Promise.all([loadFinishSummary(), loadCostSummary()]) } catch (error) { - console.error('Failed to load cost summary:', error) - toast.error('Failed to load cost analysis') - Object.assign(estimate, zeroSummary()) - Object.assign(quote, zeroSummary()) - Object.assign(actual, zeroSummary()) - hasValidQuoteData.value = false + log('Failed to load Finish Job data: %o', error) + toast.error('Failed to load job financials') + // Leave summary null and say so. Showing zeros here would tell staff a job + // is settled when we simply could not read it. + summary.value = null + loadError.value = true } finally { loading.value = false } @@ -227,6 +438,49 @@ async function loadAll() { onMounted(loadAll) +// Invoicing changed the balance, so a failed reload must say so rather than +// leave the pre-invoice figure on screen looking current. +async function reloadFinishSummary() { + try { + await loadFinishSummary() + } catch (error) { + log('Failed to reload Finish Job balance: %o', error) + summary.value = null + loadError.value = true + toast.error('Invoice saved, but the balance could not be refreshed. Reload the job.') + } +} + +// v-model has already applied the new value optimistically; this persists it and +// puts it back if the server refuses, so a tick never survives a failed save. +async function onChecklistToggle(key: ChecklistItemKey) { + const checked = checklist[key] + savingChecklistKey.value = key + try { + const response = await api.job_jobs_finish_partial_update( + { [key]: checked }, + { params: { job_id: props.jobId } }, + ) + Object.assign(checklist, response.checklist) + } catch (error) { + log('Failed to save checklist item %s: %o', key, error) + toast.error('Failed to save checklist') + checklist[key] = !checked + } finally { + savingChecklistKey.value = null + } +} + +// --- KAN-222 labour hours --- + +const labourBudgetHours = computed(() => + props.pricingMethodology === 'fixed_price' && hasValidQuoteData.value + ? quote.hours + : estimate.hours, +) +const labourRemainingHours = computed(() => Math.max(labourBudgetHours.value - actual.hours, 0)) +const labourOverrunHours = computed(() => Math.max(actual.hours - labourBudgetHours.value, 0)) + const showQuoteColumn = computed(() => { if (props.pricingMethodology === 'time_materials') return false return hasValidQuoteData.value || quote.cost > 0 || quote.rev > 0 diff --git a/frontend/src/components/job/JobInvoiceCard.vue b/frontend/src/components/job/JobInvoiceCard.vue new file mode 100644 index 000000000..3023d3e08 --- /dev/null +++ b/frontend/src/components/job/JobInvoiceCard.vue @@ -0,0 +1,469 @@ + + + diff --git a/frontend/src/components/job/JobSettingsTab.vue b/frontend/src/components/job/JobSettingsTab.vue index 54a044431..fcbac64e5 100644 --- a/frontend/src/components/job/JobSettingsTab.vue +++ b/frontend/src/components/job/JobSettingsTab.vue @@ -167,6 +167,7 @@ :initial-person-id=" typeof localJobData.person_id === 'string' ? localJobData.person_id : undefined " + :auto-select-primary="false" v-model="personDisplayValue" @update:selected-person="handlePersonSelected" /> @@ -668,11 +669,16 @@ const resetCompanyChangeState = () => { } // Handle field input changes +const NULLABLE_TEXT_FIELDS = new Set(['description', 'order_number', 'notes', 'rdti_type']) + const handleFieldInput = (field: string, value: string) => { log('[handleFieldInput] called', { field, value, isInitializing: isInitializing.value }) if (!localJobData.value) return - const newValue = value || '' + // A cleared box means unset, which is null for these columns and '' for + // the rest. + const textValue = value || '' + const newValue = NULLABLE_TEXT_FIELDS.has(field) ? value || null : textValue // Mark user as actively typing isUserTyping.value = true @@ -689,21 +695,21 @@ const handleFieldInput = (field: string, value: string) => { // Type-safe field assignment if (field === 'name') { - localJobData.value.name = newValue + localJobData.value.name = textValue } else if (field === 'description') { localJobData.value.description = newValue } else if (field === 'delivery_date') { - localJobData.value.delivery_date = newValue + localJobData.value.delivery_date = textValue } else if (field === 'order_number') { localJobData.value.order_number = newValue } else if (field === 'notes') { localJobData.value.notes = newValue } else if (field === 'pricing_methodology') { - localJobData.value.pricing_methodology = newValue as Job['pricing_methodology'] + localJobData.value.pricing_methodology = textValue as Job['pricing_methodology'] } else if (field === 'speed_quality_tradeoff') { - localJobData.value.speed_quality_tradeoff = newValue as Job['speed_quality_tradeoff'] + localJobData.value.speed_quality_tradeoff = textValue as Job['speed_quality_tradeoff'] } else if (field === 'rdti_type') { - localJobData.value.rdti_type = (newValue || null) as Job['rdti_type'] + localJobData.value.rdti_type = newValue as Job['rdti_type'] } else if (field === 'is_urgent') { const urgent = newValue === 'true' localJobData.value.is_urgent = urgent @@ -1132,11 +1138,11 @@ const handleCompanyUpdated = (updatedCompany: Company) => { toast.success('Company updated successfully') } -const handlePersonSelected = async (personLink: CompanyPerson | null) => { +const handlePersonSelected = (personLink: CompanyPerson | null) => { if (personLink) { - // Skip API call if person is already set to this value + // Skip save if person is already set to this value // This handles: - // - Scenario 4: Company change - backend sets person in response, no duplicate API call needed + // - Company change: backend sets person in response, no duplicate save needed // - User re-selecting the same person if (localJobData.value.person_id === personLink.person_id) { // Still update display value in case it's stale @@ -1153,39 +1159,27 @@ const handlePersonSelected = async (personLink: CompanyPerson | null) => { localJobData.value.person_name = personLink.person_name personDisplayValue.value = personLink.person_name - // Ensure all fields are present for Zod validation (convert undefined to null) - const personToSend = { - id: personLink.person_id, - name: personLink.person_name, - email: personLink.person_email ?? null, - } satisfies z.input - - // Save person directly (not through header autosave) - try { - await api.companies_jobs_person_update(personToSend, { - params: { job_id: props.jobId }, - }) - serverBaseline.value.person_id = personLink.person_id - serverBaseline.value.person_name = personLink.person_name - jobsStore.patchHeader(props.jobId, { - person_id: personLink.person_id, - person_name: personLink.person_name, - }) - toast.success('Person updated successfully') - } catch (error) { - toast.error('Failed to update person') - console.error('Failed to update person:', error) + if (!isInitializing.value && !isHydratingBasicInfo.value && !isSyncingFromStore.value) { + autosave.queueChange('person_id', personLink.person_id) + void autosave.flush('person-select') } } else { + // Skip save if person is already cleared, mirroring the same-value dedup + // above: a server sync can null person_id before the selector emits its + // own clear, and a redundant PATCH would bump the ETag for no change. + const hadPerson = Boolean(localJobData.value.person_id) + localJobData.value.person_id = null localJobData.value.person_name = null personDisplayValue.value = '' - if (!isInitializing.value && !isHydratingBasicInfo.value && !isSyncingFromStore.value) { - autosave.queueChanges({ - person_id: null, - person_name: null, - }) + if ( + hadPerson && + !isInitializing.value && + !isHydratingBasicInfo.value && + !isSyncingFromStore.value + ) { + autosave.queueChange('person_id', null) void autosave.flush('person-clear') } } @@ -1461,13 +1455,11 @@ const autosave = createJobAutosave({ } if (touchedKeys.includes('person_id')) { nextBaseline.person_id = serverJobDetail.person_id ?? null - localJobData.value.person_id = serverJobDetail.person_id ?? null - headerPatch.person_id = serverJobDetail.person_id ?? null - } - if (touchedKeys.includes('person_name')) { nextBaseline.person_name = serverJobDetail.person_name ?? null + localJobData.value.person_id = serverJobDetail.person_id ?? null localJobData.value.person_name = serverJobDetail.person_name ?? null personDisplayValue.value = serverJobDetail.person_name ?? '' + headerPatch.person_id = serverJobDetail.person_id ?? null headerPatch.person_name = serverJobDetail.person_name ?? null } } else { @@ -1583,15 +1575,13 @@ const autosave = createJobAutosave({ } if (touchedKeys.includes('person_id')) { const personId = coerceNullableString(partialPayload.person_id) + const personName = personId === null ? null : (localJobData.value.person_name ?? null) nextBaseline.person_id = personId - localJobData.value.person_id = personId - headerPatch.person_id = personId - } - if (touchedKeys.includes('person_name')) { - const personName = coerceNullableString(partialPayload.person_name) nextBaseline.person_name = personName + localJobData.value.person_id = personId localJobData.value.person_name = personName personDisplayValue.value = personName ?? '' + headerPatch.person_id = personId headerPatch.person_name = personName } } diff --git a/frontend/src/components/job/JobViewTabs.vue b/frontend/src/components/job/JobViewTabs.vue index 203954c77..8e0daf174 100644 --- a/frontend/src/components/job/JobViewTabs.vue +++ b/frontend/src/components/job/JobViewTabs.vue @@ -65,21 +65,17 @@
    -
    - +
    +
    ({ + finishRetrieveMock: vi.fn(), + invoicesRetrieveMock: vi.fn(), + costsSummaryRetrieveMock: vi.fn(), + createInvoiceMock: vi.fn(), + deleteInvoiceMock: vi.fn(), +})) + +vi.mock('@/api/client', () => ({ + api: { + job_jobs_finish_retrieve: finishRetrieveMock, + job_jobs_invoices_retrieve: invoicesRetrieveMock, + job_jobs_costs_summary_retrieve: costsSummaryRetrieveMock, + job_jobs_finish_partial_update: vi.fn(), + xero_create_invoice_create: createInvoiceMock, + xero_delete_invoice_destroy: deleteInvoiceMock, + }, +})) + +vi.mock('@/composables/useXeroConnection', () => ({ + useXeroConnection: () => ({ xeroConnected: { value: true } }), +})) + +vi.mock('vue-sonner', () => ({ + toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() }, +})) + +import JobFinishTab from '../JobFinishTab.vue' +import { + checklistState as checklist, + costSummary, + finishSummary as summary, + inModal, + modalText, + mountFinishTab, + resetFinishTab, +} from './finishTabFixtures' + +/** + * The backend owns every figure here. These tests assert the component renders + * what the API returned and never recomputes it — the ADR 0020 boundary that + * KAN-323 exists to restore. + */ +const mountTab = (pricingMethodology = 'fixed_price', jobStatus = 'in_progress') => + mountFinishTab(JobFinishTab, pricingMethodology, jobStatus) + +const text = (wrapper: ReturnType, id: string) => + wrapper.find(`[data-automation-id="${id}"]`).text() + +async function openInvoiceModal(wrapper: ReturnType) { + await wrapper.find('[data-automation-id="JobFinishTab-create-invoice"]').trigger('click') + await flushPromises() +} + +describe('JobFinishTab customer balance', () => { + beforeEach(() => { + vi.clearAllMocks() + finishRetrieveMock.mockResolvedValue({ summary: summary(), checklist: checklist() }) + invoicesRetrieveMock.mockResolvedValue({ invoices: [] }) + costsSummaryRetrieveMock.mockResolvedValue(costSummary()) + }) + + afterEach(resetFinishTab) + + it('renders the subtotal, GST and Total to pay returned by the API', async () => { + const wrapper = mountTab() + await flushPromises() + + expect(text(wrapper, 'JobFinishTab-remaining-excl-gst')).toContain('600') + expect(text(wrapper, 'JobFinishTab-remaining-gst')).toContain('90') + expect(text(wrapper, 'JobFinishTab-total-to-pay')).toContain('1,150') + }) + + it('does not derive Total to pay from the other figures', async () => { + // A deliberately inconsistent payload: if the component recomputed the + // total it would show 1,290 rather than the server's answer. + finishRetrieveMock.mockResolvedValue({ + summary: summary({ total_to_pay_incl_gst: 42 }), + checklist: checklist(), + }) + const wrapper = mountTab() + await flushPromises() + + expect(text(wrapper, 'JobFinishTab-total-to-pay')).toContain('42') + }) + + it('shows the outstanding invoice balance when Xero is still owed money', async () => { + const wrapper = mountTab() + await flushPromises() + + expect(text(wrapper, 'JobFinishTab-outstanding-incl-gst')).toContain('460') + }) + + it('hides the outstanding line when every invoice is paid', async () => { + finishRetrieveMock.mockResolvedValue({ + summary: summary({ outstanding_invoiced_incl_gst: 0 }), + checklist: checklist(), + }) + const wrapper = mountTab() + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-outstanding-incl-gst"]').exists()).toBe( + false, + ) + }) + + it('offers no invoice action once the job is fully covered', async () => { + finishRetrieveMock.mockResolvedValue({ + summary: summary({ + valid_invoiced_excl_gst: 1000, + remaining_to_invoice_excl_gst: 0, + remaining_gst: 0, + remaining_to_invoice_incl_gst: 0, + total_to_pay_incl_gst: 0, + }), + checklist: checklist(), + }) + const wrapper = mountTab() + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-create-invoice"]').exists()).toBe(false) + expect(wrapper.find('[data-automation-id="JobFinishTab-fully-invoiced"]').exists()).toBe(true) + }) + + it('shows an error instead of a zero balance when the load fails', async () => { + // Regression: the catch used to clear `loading` while leaving a fabricated + // zero summary on screen, so a failed request read as a settled job. + finishRetrieveMock.mockRejectedValue(new Error('boom')) + const wrapper = mountTab() + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-load-error"]').exists()).toBe(true) + expect(wrapper.find('[data-automation-id="JobFinishTab-total-to-pay"]').exists()).toBe(false) + expect(wrapper.find('[data-automation-id="JobFinishTab-create-invoice"]').exists()).toBe(false) + }) + + it('reports an over-invoiced job instead of a negative balance', async () => { + finishRetrieveMock.mockResolvedValue({ + summary: summary({ + job_value_excl_gst: 800, + valid_invoiced_excl_gst: 1000, + remaining_to_invoice_excl_gst: 0, + remaining_gst: 0, + remaining_to_invoice_incl_gst: 0, + outstanding_invoiced_incl_gst: 0, + total_to_pay_incl_gst: 0, + over_invoiced_excl_gst: 200, + }), + checklist: checklist(), + }) + const wrapper = mountTab() + await flushPromises() + + expect(text(wrapper, 'JobFinishTab-over-invoiced')).toContain('200') + }) + + it('re-reads the balance from the server after creating an invoice', async () => { + createInvoiceMock.mockResolvedValue({ success: true, messages: [] }) + const wrapper = mountTab() + await flushPromises() + expect(finishRetrieveMock).toHaveBeenCalledTimes(1) + + await openInvoiceModal(wrapper) + inModal('JobFinishTab-mode-invoice-full')!.click() + await flushPromises() + + expect(createInvoiceMock).toHaveBeenCalled() + expect(finishRetrieveMock).toHaveBeenCalledTimes(2) + }) + + it('re-reads the balance from the server after deleting an invoice', async () => { + invoicesRetrieveMock.mockResolvedValue({ + invoices: [ + { + id: 'inv-1', + xero_id: 'xero-1', + number: 'INV-001', + status: 'AUTHORISED', + date: '2026-08-01', + total_excl_tax: 400, + online_url: 'https://xero.example/inv-1', + }, + ], + }) + deleteInvoiceMock.mockResolvedValue({}) + const wrapper = mountTab() + await flushPromises() + + await wrapper.get('[data-automation-id="JobInvoiceCard-delete-inv-1"]').trigger('click') + await flushPromises() + + expect(deleteInvoiceMock).toHaveBeenCalled() + expect(finishRetrieveMock).toHaveBeenCalledTimes(2) + }) +}) + +describe('JobFinishTab invoice modes', () => { + beforeEach(() => { + vi.clearAllMocks() + finishRetrieveMock.mockResolvedValue({ summary: summary(), checklist: checklist() }) + invoicesRetrieveMock.mockResolvedValue({ invoices: [] }) + costsSummaryRetrieveMock.mockResolvedValue(costSummary()) + }) + + afterEach(resetFinishTab) + + it('keeps all three fixed-price modes available', async () => { + const wrapper = mountTab('fixed_price') + await flushPromises() + await openInvoiceModal(wrapper) + + expect(inModal('JobFinishTab-mode-invoice-full')).not.toBeNull() + expect(inModal('JobFinishTab-mode-invoice-percent')).not.toBeNull() + expect(inModal('JobFinishTab-mode-invoice-amount')).not.toBeNull() + }) + + it('keeps both T&M modes available', async () => { + const wrapper = mountTab('time_materials') + await flushPromises() + await openInvoiceModal(wrapper) + + expect(inModal('JobFinishTab-mode-invoice-costs-to-date')).not.toBeNull() + expect(inModal('JobFinishTab-mode-invoice-amount')).not.toBeNull() + expect(inModal('JobFinishTab-mode-invoice-percent')).toBeNull() + }) + + it('offers invoicing before the work is complete', async () => { + const wrapper = mountTab('fixed_price', 'approved') + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-create-invoice"]').exists()).toBe(true) + await openInvoiceModal(wrapper) + expect(modalText('JobFinishTab-mode-invoice-full')).toContain('in advance') + }) + + it('describes the remaining balance once the work is complete', async () => { + const wrapper = mountTab('fixed_price', 'recently_completed') + await flushPromises() + await openInvoiceModal(wrapper) + + expect(modalText('JobFinishTab-mode-invoice-full')).toContain('remaining quote balance') + }) +}) diff --git a/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts b/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts new file mode 100644 index 000000000..c91bb1450 --- /dev/null +++ b/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts @@ -0,0 +1,158 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { flushPromises } from '@vue/test-utils' + +const { + finishRetrieveMock, + invoicesRetrieveMock, + costsSummaryRetrieveMock, + checklistUpdateMock, + toastErrorMock, +} = vi.hoisted(() => ({ + finishRetrieveMock: vi.fn(), + invoicesRetrieveMock: vi.fn(), + costsSummaryRetrieveMock: vi.fn(), + checklistUpdateMock: vi.fn(), + toastErrorMock: vi.fn(), +})) + +vi.mock('@/api/client', () => ({ + api: { + job_jobs_finish_retrieve: finishRetrieveMock, + job_jobs_invoices_retrieve: invoicesRetrieveMock, + job_jobs_costs_summary_retrieve: costsSummaryRetrieveMock, + job_jobs_finish_partial_update: checklistUpdateMock, + xero_create_invoice_create: vi.fn(), + xero_delete_invoice_destroy: vi.fn(), + }, +})) + +vi.mock('@/composables/useXeroConnection', () => ({ + useXeroConnection: () => ({ xeroConnected: { value: true } }), +})) + +vi.mock('vue-sonner', () => ({ + toast: { error: toastErrorMock, success: vi.fn(), warning: vi.fn() }, +})) + +import JobFinishTab from '../JobFinishTab.vue' +import { + CHECKLIST_ITEMS as ITEMS, + checklistState as checklist, + costSummary, + finishSummary as summary, + mountFinishTab, + resetFinishTab, +} from './finishTabFixtures' + +const mountTab = (pricingMethodology = 'fixed_price') => + mountFinishTab(JobFinishTab, pricingMethodology) + +const item = (wrapper: ReturnType, key: string) => + wrapper.find(`[data-automation-id="JobFinishTab-checklist-${key}"]`) + +describe('JobFinishTab completion checklist', () => { + beforeEach(() => { + vi.clearAllMocks() + finishRetrieveMock.mockResolvedValue({ summary: summary(), checklist: checklist() }) + invoicesRetrieveMock.mockResolvedValue({ invoices: [] }) + costsSummaryRetrieveMock.mockResolvedValue(costSummary()) + }) + + afterEach(resetFinishTab) + + it('asks the same five questions whatever the pricing methodology', async () => { + for (const methodology of ['fixed_price', 'time_materials']) { + const wrapper = mountTab(methodology) + await flushPromises() + + for (const key of ITEMS) { + expect(item(wrapper, key).exists(), `${methodology}/${key}`).toBe(true) + } + + resetFinishTab() + } + }) + + it('warns that time and materials are what a T&M customer pays', async () => { + const wrapper = mountTab('time_materials') + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-tm-urgency"]').text()).toContain( + 'before invoicing', + ) + }) + + it('does not show the T&M urgency note on a quoted job', async () => { + const wrapper = mountTab('fixed_price') + await flushPromises() + + expect(wrapper.find('[data-automation-id="JobFinishTab-tm-urgency"]').exists()).toBe(false) + }) + + it('reflects ticks already recorded on the job', async () => { + finishRetrieveMock.mockResolvedValue({ + summary: summary(), + checklist: checklist({ foreman_signed_off: true }), + }) + const wrapper = mountTab() + await flushPromises() + + expect((item(wrapper, 'foreman_signed_off').element as HTMLInputElement).checked).toBe(true) + expect((item(wrapper, 'released').element as HTMLInputElement).checked).toBe(false) + }) + + it('sends only the item that changed', async () => { + checklistUpdateMock.mockResolvedValue({ + summary: summary(), + checklist: checklist({ materials_checked: true }), + }) + const wrapper = mountTab() + await flushPromises() + + await item(wrapper, 'materials_checked').setValue(true) + await flushPromises() + + expect(checklistUpdateMock).toHaveBeenCalledWith( + { materials_checked: true }, + { params: { job_id: 'job-1' } }, + ) + }) + + it('sends false when a tick is withdrawn', async () => { + finishRetrieveMock.mockResolvedValue({ + summary: summary(), + checklist: checklist({ released: true }), + }) + checklistUpdateMock.mockResolvedValue({ summary: summary(), checklist: checklist() }) + const wrapper = mountTab() + await flushPromises() + + await item(wrapper, 'released').setValue(false) + await flushPromises() + + expect(checklistUpdateMock).toHaveBeenCalledWith( + { released: false }, + { params: { job_id: 'job-1' } }, + ) + }) + + it('reverts the box and warns the user when the save fails', async () => { + checklistUpdateMock.mockRejectedValue(new Error('nope')) + const wrapper = mountTab() + await flushPromises() + + await item(wrapper, 'foreman_signed_off').setValue(true) + await flushPromises() + + expect(toastErrorMock).toHaveBeenCalledWith('Failed to save checklist') + expect((item(wrapper, 'foreman_signed_off').element as HTMLInputElement).checked).toBe(false) + }) + + it('does not hide the invoice action behind the checklist', async () => { + const wrapper = mountTab() + await flushPromises() + + expect((item(wrapper, 'foreman_signed_off').element as HTMLInputElement).checked).toBe(false) + expect(wrapper.find('[data-automation-id="JobFinishTab-create-invoice"]').exists()).toBe(true) + }) +}) diff --git a/frontend/src/components/job/__tests__/JobFinishTab.labourHours.test.ts b/frontend/src/components/job/__tests__/JobFinishTab.labourHours.test.ts new file mode 100644 index 000000000..fb27400f7 --- /dev/null +++ b/frontend/src/components/job/__tests__/JobFinishTab.labourHours.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { flushPromises } from '@vue/test-utils' + +const { finishRetrieveMock, invoicesRetrieveMock, costsSummaryRetrieveMock } = vi.hoisted(() => ({ + finishRetrieveMock: vi.fn(), + invoicesRetrieveMock: vi.fn(), + costsSummaryRetrieveMock: vi.fn(), +})) + +vi.mock('@/api/client', () => ({ + api: { + job_jobs_finish_retrieve: finishRetrieveMock, + job_jobs_invoices_retrieve: invoicesRetrieveMock, + job_jobs_costs_summary_retrieve: costsSummaryRetrieveMock, + job_jobs_finish_partial_update: vi.fn(), + xero_create_invoice_create: vi.fn(), + xero_delete_invoice_destroy: vi.fn(), + }, +})) + +vi.mock('@/composables/useXeroConnection', () => ({ + useXeroConnection: () => ({ xeroConnected: { value: true } }), +})) + +vi.mock('vue-sonner', () => ({ + toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() }, +})) + +import JobFinishTab from '../JobFinishTab.vue' +import { + checklistState, + costSummary, + finishSummary, + mountFinishTab, + resetFinishTab, +} from './finishTabFixtures' + +/** + * KAN-222: office staff read budget, used and remaining without doing the + * subtraction, and an overrun shows as a positive number rather than a negative + * remainder. + */ +async function mountWithHours( + hours: { estimate: number; quote: number; actual: number }, + pricingMethodology = 'fixed_price', +) { + costsSummaryRetrieveMock.mockResolvedValue(costSummary(hours)) + const wrapper = mountFinishTab(JobFinishTab, pricingMethodology) + await flushPromises() + return wrapper +} + +const tile = (wrapper: ReturnType, which: string) => + wrapper.find(`[data-automation-id="JobFinishTab-labour-${which}"]`).text() + +describe('JobFinishTab labour hours', () => { + beforeEach(() => { + vi.clearAllMocks() + finishRetrieveMock.mockResolvedValue({ + summary: finishSummary(), + checklist: checklistState(), + }) + invoicesRetrieveMock.mockResolvedValue({ invoices: [] }) + }) + + afterEach(resetFinishTab) + + it('reports budget, used and remaining-or-overrun', async () => { + const cases = [ + { label: 'under budget', actual: 8, remaining: '4', heading: 'Hours remaining' }, + { label: 'exactly on budget', actual: 12, remaining: '0', heading: 'Hours remaining' }, + { label: 'over budget', actual: 15, remaining: '3', heading: 'Overrun' }, + ] + + for (const { label, actual, remaining, heading } of cases) { + const wrapper = await mountWithHours({ estimate: 10, quote: 12, actual }) + + expect(tile(wrapper, 'budget'), label).toBe('12') + expect(tile(wrapper, 'used'), label).toBe(String(actual)) + expect(tile(wrapper, 'remaining'), label).toBe(remaining) + expect(tile(wrapper, 'remaining'), label).not.toContain('-') + expect( + wrapper.find('[data-automation-id="JobFinishTab-labour-hours"]').text(), + label, + ).toContain(heading) + + resetFinishTab() + } + }) + + it('budgets from quote hours, falling back to estimate hours', async () => { + const cases = [ + { label: 'fixed price with a quote', quote: 12, methodology: 'fixed_price', budget: '12' }, + { label: 'fixed price, no quote', quote: 0, methodology: 'fixed_price', budget: '10' }, + { label: 'time and materials', quote: 12, methodology: 'time_materials', budget: '10' }, + ] + + for (const { label, quote, methodology, budget } of cases) { + const wrapper = await mountWithHours({ estimate: 10, quote, actual: 5 }, methodology) + + expect(tile(wrapper, 'budget'), label).toBe(budget) + + resetFinishTab() + } + }) +}) diff --git a/frontend/src/components/job/__tests__/JobSettingsTab.companyChange.test.ts b/frontend/src/components/job/__tests__/JobSettingsTab.companyChange.test.ts index 375972f50..4057185d6 100644 --- a/frontend/src/components/job/__tests__/JobSettingsTab.companyChange.test.ts +++ b/frontend/src/components/job/__tests__/JobSettingsTab.companyChange.test.ts @@ -2,18 +2,19 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { defineComponent, h } from 'vue' -const { capturedConfig, updateJobHeaderPartial } = vi.hoisted(() => ({ +const { capturedConfig, updateJobHeaderPartial, queueChange } = vi.hoisted(() => ({ capturedConfig: { value: null as { saveAdapter: (patch: Record) => unknown } | null, }, updateJobHeaderPartial: vi.fn(), + queueChange: vi.fn(), })) vi.mock('@/composables/useJobAutosave', () => ({ createJobAutosave: (config: { saveAdapter: (patch: Record) => unknown }) => { capturedConfig.value = config return { - queueChange: vi.fn(), + queueChange, queueChanges: vi.fn(), flush: vi.fn(), cancel: vi.fn(), @@ -76,7 +77,6 @@ vi.mock('@/api/client', () => ({ is_urgent: false, }), workflow_xero_pay_items_list: vi.fn().mockResolvedValue([]), - companies_jobs_person_retrieve: vi.fn().mockResolvedValue({ id: null, name: null }), }, })) @@ -178,3 +178,54 @@ describe('JobSettingsTab company-change autosave', () => { expect(Object.keys(payload)).not.toContain('person_name') }) }) + +describe('JobSettingsTab person clear autosave', () => { + const personSelectorStub = defineComponent({ + name: 'PersonSelectorStub', + emits: ['update:selectedPerson', 'update:modelValue'], + setup: () => () => h('div'), + }) + + beforeEach(() => { + vi.clearAllMocks() + }) + + it('saves the first clear but not a repeat clear of an already-empty person', async () => { + const wrapper = mount(JobSettingsTab, { + props: { + jobId: 'job-1', + jobNumber: '101', + pricingMethodology: 'time_materials', + quoted: false, + fullyInvoiced: false, + }, + global: { + stubs: { + Card: passthrough, + CardHeader: passthrough, + CardTitle: passthrough, + CardDescription: passthrough, + CardContent: passthrough, + RichTextEditor: { template: '
    ' }, + CompanyLookup: { template: '
    ' }, + PersonSelector: personSelectorStub, + CreateCompanyModal: { template: '
    ' }, + }, + }, + }) + await flushPromises() + + const selector = wrapper.findComponent(personSelectorStub) + + // Job loads with person-1 set, so the first clear is a real change. + selector.vm.$emit('update:selectedPerson', null) + await flushPromises() + expect(queueChange).toHaveBeenCalledWith('person_id', null) + + // Second clear changes nothing and must not queue a redundant save. + queueChange.mockClear() + selector.vm.$emit('update:selectedPerson', null) + await flushPromises() + expect(queueChange).not.toHaveBeenCalled() + }) +}) diff --git a/frontend/src/components/job/__tests__/JobSettingsTab.labourRate.test.ts b/frontend/src/components/job/__tests__/JobSettingsTab.labourRate.test.ts index 3c647742b..49fe87622 100644 --- a/frontend/src/components/job/__tests__/JobSettingsTab.labourRate.test.ts +++ b/frontend/src/components/job/__tests__/JobSettingsTab.labourRate.test.ts @@ -70,7 +70,6 @@ vi.mock('@/api/client', () => ({ is_urgent: false, }), workflow_xero_pay_items_list: vi.fn().mockResolvedValue([]), - companies_jobs_person_retrieve: vi.fn().mockResolvedValue({ id: null, name: null }), }, })) diff --git a/frontend/src/components/job/__tests__/JobSettingsTab.urgent.test.ts b/frontend/src/components/job/__tests__/JobSettingsTab.urgent.test.ts index a1ae53cae..70bdb519e 100644 --- a/frontend/src/components/job/__tests__/JobSettingsTab.urgent.test.ts +++ b/frontend/src/components/job/__tests__/JobSettingsTab.urgent.test.ts @@ -68,7 +68,6 @@ vi.mock('@/api/client', () => ({ is_urgent: true, }), workflow_xero_pay_items_list: vi.fn().mockResolvedValue([]), - companies_jobs_person_retrieve: vi.fn().mockResolvedValue({ id: null, name: null }), }, })) diff --git a/frontend/src/components/job/__tests__/finishTabFixtures.ts b/frontend/src/components/job/__tests__/finishTabFixtures.ts new file mode 100644 index 000000000..2162fea4c --- /dev/null +++ b/frontend/src/components/job/__tests__/finishTabFixtures.ts @@ -0,0 +1,99 @@ +import { vi } from 'vitest' +import { mount } from '@vue/test-utils' +import type { Component } from 'vue' + +/** Shared fixtures for the JobFinishTab specs, which all mount the same tree. */ + +const baseFinishSummary = { + job_value_excl_gst: 1000, + valid_invoiced_excl_gst: 400, + outstanding_invoiced_incl_gst: 460, + remaining_to_invoice_excl_gst: 600, + remaining_gst: 90, + remaining_to_invoice_incl_gst: 690, + total_to_pay_incl_gst: 1150, + over_invoiced_excl_gst: 0, +} + +const baseChecklistState = { + foreman_signed_off: false, + timesheets_collected: false, + materials_checked: false, + customer_called: false, + released: false, +} + +// Typed against the base shapes so a typo in an override is a compile error +// rather than a silently ignored key that leaves the default in place. +export const finishSummary = (overrides: Partial = {}) => ({ + ...baseFinishSummary, + ...overrides, +}) + +export const checklistState = (overrides: Partial = {}) => ({ + ...baseChecklistState, + ...overrides, +}) + +export const CHECKLIST_ITEMS = [ + 'foreman_signed_off', + 'timesheets_collected', + 'materials_checked', + 'customer_called', + 'released', +] as const + +export const costSummary = ( + hours: { estimate: number; quote: number; actual: number } = { + estimate: 10, + quote: 12, + actual: 11, + }, +) => ({ + estimate: { cost: 500, rev: 800, hours: hours.estimate, profitMargin: 60 }, + quote: hours.quote > 0 ? { cost: 600, rev: 1000, hours: hours.quote, profitMargin: 66 } : null, + actual: { cost: 550, rev: 900, hours: hours.actual, profitMargin: 63 }, +}) + +let mounted: ReturnType | null = null + +export function mountFinishTab( + component: Component, + pricingMethodology = 'fixed_price', + jobStatus = 'in_progress', +) { + // Mounted to document.body because the invoice modal renders through a + // reka-ui portal; resetFinishTab() must run in afterEach or the next test + // finds the previous modal. + mounted = mount(component, { + props: { jobId: 'job-1', pricingMethodology, jobStatus }, + attachTo: document.body, + }) + return mounted +} + +export function resetFinishTab() { + mounted?.unmount() + mounted = null + document.body.innerHTML = '' +} + +export const inModal = (id: string) => + document.body.querySelector(`[data-automation-id="${id}"]`) + +export const modalText = (id: string) => { + const el = inModal(id) + if (!el) throw new Error(`${id} is not in the modal`) + return el.textContent ?? '' +} + +/** The api-client mock shape every JobFinishTab spec needs. */ +export const finishTabApiMocks = () => ({ + finishRetrieveMock: vi.fn(), + invoicesRetrieveMock: vi.fn(), + costsSummaryRetrieveMock: vi.fn(), + checklistUpdateMock: vi.fn(), + createInvoiceMock: vi.fn(), + deleteInvoiceMock: vi.fn(), + toastErrorMock: vi.fn(), +}) diff --git a/frontend/src/components/process-documents/ProcessDocumentModal.vue b/frontend/src/components/process-documents/ProcessDocumentModal.vue index ba146f209..6a8528685 100644 --- a/frontend/src/components/process-documents/ProcessDocumentModal.vue +++ b/frontend/src/components/process-documents/ProcessDocumentModal.vue @@ -108,7 +108,7 @@ interface EditableDocument { title: string document_number?: string | null tags?: string[] | unknown - site_location?: string + site_location?: string | null } interface Props { @@ -210,15 +210,15 @@ async function submitForm() { if (isForm.value) { await store.updateForm(props.category, props.editDocument.id, { title: form.value.title, - document_number: form.value.document_number || '', + document_number: form.value.document_number || null, tags: parseTags(form.value.tags), }) } else { await store.updateProcedure(props.category, props.editDocument.id, { title: form.value.title, - document_number: form.value.document_number || '', + document_number: form.value.document_number || null, tags: parseTags(form.value.tags), - site_location: form.value.site_location || '', + site_location: form.value.site_location || null, }) } } else { diff --git a/frontend/src/components/process-documents/safety-wizard/SafetyWizardModal.vue b/frontend/src/components/process-documents/safety-wizard/SafetyWizardModal.vue index b3cf2ce41..2a7394901 100644 --- a/frontend/src/components/process-documents/safety-wizard/SafetyWizardModal.vue +++ b/frontend/src/components/process-documents/safety-wizard/SafetyWizardModal.vue @@ -403,11 +403,7 @@ function rejectImprovement(field: SafetyDocumentStringField) { // Only the plain-string fields are editable as free text; `document_type` is a // closed union and the rest are arrays/objects. type SafetyDocumentStringField = - | 'title' - | 'description' - | 'site_location' - | 'additional_notes' - | 'raw_text' + 'title' | 'description' | 'site_location' | 'additional_notes' | 'raw_text' function updateField(field: SafetyDocumentStringField, value: string) { if (content.value) { diff --git a/frontend/src/components/purchasing/PoLinesTable.vue b/frontend/src/components/purchasing/PoLinesTable.vue index 6d679daf6..cfcf22655 100644 --- a/frontend/src/components/purchasing/PoLinesTable.vue +++ b/frontend/src/components/purchasing/PoLinesTable.vue @@ -107,7 +107,7 @@ function makeEmptyLine(): PurchaseOrderLine { unit_cost: 0, price_tbc: false, supplier_item_code: undefined, - item_code: '', + item_code: undefined, received_quantity: undefined, metal_type: undefined, alloy: undefined, diff --git a/frontend/src/composables/__tests__/useDataFreshness.test.ts b/frontend/src/composables/__tests__/useDataFreshness.test.ts new file mode 100644 index 000000000..a6f70a9df --- /dev/null +++ b/frontend/src/composables/__tests__/useDataFreshness.test.ts @@ -0,0 +1,98 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { retrieveDataVersions } = vi.hoisted(() => ({ + retrieveDataVersions: vi.fn(), +})) + +vi.mock('@/api/client', () => ({ + api: { + data_versions_retrieve: retrieveDataVersions, + }, +})) + +import { dataFreshness } from '@/composables/useDataFreshness' + +describe('useDataFreshness', () => { + beforeEach(() => { + vi.clearAllMocks() + dataFreshness._resetForTesting() + }) + + it('passes the previous and current opaque versions to stale subscribers', async () => { + const onStale = vi.fn() + dataFreshness.subscribe('kanban', onStale) + retrieveDataVersions + .mockResolvedValueOnce({ kanban: 'version-1' }) + .mockResolvedValueOnce({ kanban: 'version-2' }) + + await dataFreshness.checkFreshness() + await dataFreshness.checkFreshness() + + expect(onStale).toHaveBeenCalledWith('version-1', 'version-2') + }) + + it('does not let a stale unsubscribe remove a newer subscriber bucket', async () => { + const firstSubscriber = vi.fn() + const unsubscribeFirst = dataFreshness.subscribe('kanban', firstSubscriber) + retrieveDataVersions.mockResolvedValueOnce({ kanban: 'version-1' }) + await dataFreshness.checkFreshness() + + unsubscribeFirst() + const newerSubscriber = vi.fn() + dataFreshness.subscribe('kanban', newerSubscriber) + unsubscribeFirst() + retrieveDataVersions.mockResolvedValueOnce({ kanban: 'version-2' }) + + await dataFreshness.checkFreshness() + + expect(newerSubscriber).toHaveBeenCalledWith('version-1', 'version-2') + }) + + it('runs every callback, surfaces the original failure, and retries only its dataset', async () => { + const failure = new Error('incremental refresh failed') + const failingKanbanSubscriber = vi + .fn() + .mockRejectedValueOnce(failure) + .mockResolvedValueOnce(undefined) + const successfulKanbanSubscriber = vi.fn() + const stockSubscriber = vi.fn() + dataFreshness.subscribe('kanban', failingKanbanSubscriber) + dataFreshness.subscribe('kanban', successfulKanbanSubscriber) + dataFreshness.subscribe('stock', stockSubscriber) + retrieveDataVersions + .mockResolvedValueOnce({ kanban: 'kanban-1', stock: 'stock-1' }) + .mockResolvedValue({ kanban: 'kanban-2', stock: 'stock-2' }) + + await dataFreshness.checkFreshness() + await expect(dataFreshness.checkFreshness()).rejects.toBe(failure) + await dataFreshness.checkFreshness() + + expect(failingKanbanSubscriber).toHaveBeenCalledTimes(2) + expect(successfulKanbanSubscriber).toHaveBeenCalledTimes(2) + expect(successfulKanbanSubscriber).toHaveBeenNthCalledWith(2, 'kanban-1', 'kanban-2') + expect(stockSubscriber).toHaveBeenCalledOnce() + expect(stockSubscriber).toHaveBeenCalledWith('stock-1', 'stock-2') + }) + + it('aggregates multiple callback failures after every callback runs', async () => { + const firstFailure = new Error('first failure') + const secondFailure = new Error('second failure') + const firstSubscriber = vi.fn().mockRejectedValue(firstFailure) + const secondSubscriber = vi.fn().mockRejectedValue(secondFailure) + dataFreshness.subscribe('kanban', firstSubscriber) + dataFreshness.subscribe('kanban', secondSubscriber) + retrieveDataVersions + .mockResolvedValueOnce({ kanban: 'version-1' }) + .mockResolvedValueOnce({ kanban: 'version-2' }) + + await dataFreshness.checkFreshness() + const result = dataFreshness.checkFreshness() + + await expect(result).rejects.toBeInstanceOf(AggregateError) + await expect(result).rejects.toMatchObject({ + errors: [firstFailure, secondFailure], + }) + expect(firstSubscriber).toHaveBeenCalledOnce() + expect(secondSubscriber).toHaveBeenCalledOnce() + }) +}) diff --git a/frontend/src/composables/__tests__/useOptimizedKanban.test.ts b/frontend/src/composables/__tests__/useOptimizedKanban.test.ts index cbfceef28..3b41cd2d7 100644 --- a/frontend/src/composables/__tests__/useOptimizedKanban.test.ts +++ b/frontend/src/composables/__tests__/useOptimizedKanban.test.ts @@ -12,7 +12,9 @@ const { updateJobStatus, reorderJob, performAdvancedSearch, + getKanbanChanges, checkFreshness, + freshnessSubscribers, saveFeedback, kanbanColumnCache, kanbanJobsById, @@ -27,7 +29,12 @@ const { updateJobStatus: vi.fn(), reorderJob: vi.fn(), performAdvancedSearch: vi.fn(), + getKanbanChanges: vi.fn(), checkFreshness: vi.fn(), + freshnessSubscribers: new Map< + string, + (previousVersion: string, currentVersion: string) => void | Promise + >(), saveFeedback: { pending: vi.fn(), saving: vi.fn(), @@ -62,6 +69,7 @@ vi.mock('@/services/job.service', () => ({ tooltips: {}, }), performAdvancedSearch, + getKanbanChanges, updateJobStatus, reorderJob, }, @@ -69,7 +77,6 @@ vi.mock('@/services/job.service', () => ({ vi.mock('@/stores/jobs', () => ({ useJobsStore: () => ({ - kanbanCacheGeneration: 0, getKanbanColumnCache: (columnId: string) => kanbanColumnCache.get(columnId) ?? null, getKanbanColumnJobs: (columnId: string) => { const cached = kanbanColumnCache.get(columnId) as { jobIds?: string[] } | undefined @@ -141,12 +148,29 @@ vi.mock('@/stores/jobs', () => ({ }, setCurrentContext, setLoadingKanban, + setKanbanJob: (job: Record) => { + kanbanJobsById.set(String(job.id), job) + }, + removeKanbanJob: (jobId: string) => { + kanbanJobsById.delete(jobId) + }, + clearKanbanColumnCache: () => { + kanbanColumnCache.clear() + kanbanJobsById.clear() + }, }), })) vi.mock('../useDataFreshness', () => ({ dataFreshness: { checkFreshness, + subscribe: ( + key: string, + callback: (previousVersion: string, currentVersion: string) => void | Promise, + ) => { + freshnessSubscribers.set(key, callback) + return vi.fn() + }, }, })) @@ -236,7 +260,14 @@ describe('useOptimizedKanban search reconciliation', () => { route.query = {} kanbanColumnCache.clear() kanbanJobsById.clear() + freshnessSubscribers.clear() checkFreshness.mockResolvedValue(undefined) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [], + removed_job_ids: [], + full_refresh_required: false, + }) updateJobStatus.mockResolvedValue({ success: true }) reorderJob.mockResolvedValue({ success: true }) getJobsByColumn.mockImplementation(async (columnId: string) => { @@ -304,6 +335,432 @@ describe('useOptimizedKanban search reconciliation', () => { expect(kanban.getJobsByStatus.value('in_progress').map((job) => job.id)).toEqual(['job-1']) }) + it('merges a content-only freshness change without reloading any column', async () => { + const unchangedJob = buildKanbanJob({ id: 'job-2', name: 'Unchanged' }) + getJobsByColumn.mockImplementation(async (columnId: string) => { + if (columnId === 'in_progress') { + return { + success: true, + jobs: [buildKanbanJob(), unchangedJob], + total: 2, + filtered_count: 2, + has_more: false, + } + } + return { success: true, jobs: [], total: 0, filtered_count: 0, has_more: false } + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [buildKanbanJob({ name: 'Updated card', updated_at: '2026-05-15T00:00:00Z' })], + removed_job_ids: [], + full_refresh_required: false, + }) + + const kanban = await mountHarness() + getJobsByColumn.mockClear() + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(getKanbanChanges).toHaveBeenCalledWith('old-version') + expect(getJobsByColumn).not.toHaveBeenCalled() + expect(kanban.getJobsByStatus.value('in_progress').map((job) => job.name)).toEqual([ + 'Updated card', + 'Unchanged', + ]) + }) + + it('reconciles quick-search membership authoritatively after incremental changes', async () => { + const leavingJob = buildKanbanJob({ id: 'job-leaving', name: 'Kick plate' }) + const enteringJob = buildKanbanJob({ id: 'job-entering', name: 'Plain plate' }) + getJobsByColumn.mockImplementation(async (columnId: string) => { + if (columnId === 'in_progress') { + return { + success: true, + jobs: [leavingJob, enteringJob], + total: 2, + filtered_count: 2, + has_more: false, + } + } + return { success: true, jobs: [], total: 0, filtered_count: 0, has_more: false } + }) + performAdvancedSearch.mockResolvedValueOnce({ jobs: [leavingJob] }).mockResolvedValueOnce({ + jobs: [ + buildKanbanJob({ + id: 'job-entering', + name: 'Kick plate revision', + updated_at: '2026-05-15T00:00:00Z', + }), + ], + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [ + buildKanbanJob({ + id: 'job-leaving', + name: 'No longer matching', + updated_at: '2026-05-15T00:00:00Z', + }), + buildKanbanJob({ + id: 'job-entering', + name: 'Kick plate revision', + updated_at: '2026-05-15T00:00:00Z', + }), + ], + removed_job_ids: [], + full_refresh_required: false, + }) + + const kanban = await mountHarness() + kanban.searchQuery.value = 'kick' + await kanban.handleSearch() + await vi.advanceTimersByTimeAsync(300) + await flushPromises() + + expect(kanban.filteredJobs.value.map((job) => job.id)).toEqual(['job-leaving']) + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(performAdvancedSearch).toHaveBeenCalledTimes(2) + expect(kanban.filteredJobs.value.map((job) => job.id)).toEqual(['job-entering']) + }) + + it('keeps zero-result advanced search active and refreshes its membership', async () => { + const matchingJob = buildKanbanJob({ id: 'job-advanced', name: 'Advanced match' }) + performAdvancedSearch + .mockResolvedValueOnce({ jobs: [] }) + .mockResolvedValueOnce({ jobs: [matchingJob] }) + .mockResolvedValueOnce({ jobs: [] }) + .mockResolvedValueOnce({ jobs: [] }) + getKanbanChanges + .mockResolvedValueOnce({ + success: true, + jobs: [matchingJob], + removed_job_ids: [], + full_refresh_required: false, + }) + .mockResolvedValueOnce({ + success: true, + jobs: [], + removed_job_ids: [], + full_refresh_required: true, + }) + + const kanban = await mountHarness() + kanban.advancedFilters.value.name = 'Advanced' + kanban.advancedFilters.value.status = ['draft', 'approved'] + await kanban.handleAdvancedSearch() + + expect(kanban.isSearchActive.value).toBe(true) + expect(kanban.getJobsByStatus.value('in_progress')).toEqual([]) + + await freshnessSubscribers.get('kanban')?.('version-1', 'version-2') + + expect(kanban.filteredJobs.value.map((job) => job.id)).toEqual(['job-advanced']) + + await freshnessSubscribers.get('kanban')?.('version-2', 'version-3') + await freshnessSubscribers.get('kanban_related')?.('related-1', 'related-2') + + expect(performAdvancedSearch).toHaveBeenCalledTimes(4) + expect(performAdvancedSearch).toHaveBeenLastCalledWith( + expect.objectContaining({ + name: 'Advanced', + status: ['draft', 'approved'], + }), + ) + expect(kanban.isSearchActive.value).toBe(true) + expect(kanban.getJobsByStatus.value('in_progress')).toEqual([]) + }) + + it('revalidates only the old and new columns for a structural freshness change', async () => { + const unchangedJob = buildKanbanJob({ id: 'job-2', name: 'Unchanged' }) + const archivedJob = buildKanbanJob({ + id: 'job-archived', + name: 'Archived', + status: 'archived', + status_key: 'archived', + }) + getJobsByColumn.mockImplementation(async (columnId: string) => { + if (columnId === 'in_progress') { + return { + success: true, + jobs: [buildKanbanJob(), unchangedJob], + total: 2, + filtered_count: 2, + has_more: false, + } + } + if (columnId === 'archived') { + return { + success: true, + jobs: [archivedJob], + total: 1, + filtered_count: 1, + has_more: false, + } + } + return { success: true, jobs: [], total: 0, filtered_count: 0, has_more: false } + }) + + const kanban = await mountHarness() + const movedJob = buildKanbanJob({ + status: 'draft', + status_key: 'draft', + priority: 200, + updated_at: '2026-05-15T00:00:00Z', + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [movedJob], + removed_job_ids: [], + full_refresh_required: false, + }) + getJobsByColumn.mockClear() + getJobsByColumn.mockImplementation(async (columnId: string) => { + if (columnId === 'draft') { + return { + success: true, + jobs: [movedJob], + total: 1, + filtered_count: 1, + has_more: false, + } + } + if (columnId === 'in_progress') { + return { + success: true, + jobs: [unchangedJob], + total: 1, + filtered_count: 1, + has_more: false, + } + } + throw new Error(`Unexpected revalidation of ${columnId}`) + }) + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(getJobsByColumn.mock.calls.map(([columnId]) => columnId).sort()).toEqual([ + 'draft', + 'in_progress', + ]) + expect(kanban.getJobsByStatus.value('draft').map((job) => job.id)).toEqual(['job-1']) + expect(kanban.getJobsByStatus.value('in_progress').map((job) => job.id)).toEqual(['job-2']) + expect(kanban.getJobsByStatus.value('archived').map((job) => job.id)).toEqual(['job-archived']) + }) + + it('revalidates only one column when priority changes within the same status', async () => { + const kanban = await mountHarness() + const reprioritizedJob = buildKanbanJob({ + priority: 200, + updated_at: '2026-05-15T00:00:00Z', + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [reprioritizedJob], + removed_job_ids: [], + full_refresh_required: false, + }) + getJobsByColumn.mockClear() + getJobsByColumn.mockResolvedValue({ + success: true, + jobs: [reprioritizedJob], + total: 1, + filtered_count: 1, + has_more: false, + }) + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(getJobsByColumn).toHaveBeenCalledOnce() + expect(getJobsByColumn).toHaveBeenCalledWith('in_progress') + expect(kanban.getJobsByStatus.value('in_progress')[0]?.priority).toBe(200) + }) + + it('rejects failed freshness revalidation so the same old token can be retried', async () => { + const kanban = await mountHarness() + const reprioritizedJob = buildKanbanJob({ + priority: 200, + updated_at: '2026-05-15T00:00:00Z', + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [reprioritizedJob], + removed_job_ids: [], + full_refresh_required: false, + }) + getJobsByColumn.mockRejectedValueOnce(new Error('column unavailable')).mockResolvedValueOnce({ + success: true, + jobs: [reprioritizedJob], + total: 1, + filtered_count: 1, + has_more: false, + }) + + const refresh = freshnessSubscribers.get('kanban') + await expect(refresh?.('old-version', 'new-version')).rejects.toThrow( + 'Failed to revalidate changed Kanban columns', + ) + await refresh?.('old-version', 'new-version') + + expect(getKanbanChanges).toHaveBeenNthCalledWith(1, 'old-version') + expect(getKanbanChanges).toHaveBeenNthCalledWith(2, 'old-version') + expect(kanban.getJobsByStatus.value('in_progress')[0]?.priority).toBe(200) + }) + + it('rejects an unsafe full refresh when any required column fails', async () => { + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [], + removed_job_ids: [], + full_refresh_required: true, + }) + await mountHarness() + getJobsByColumn.mockImplementation(async (columnId: string) => { + if (columnId === 'draft') { + throw new Error('draft unavailable') + } + return { success: true, jobs: [], total: 0, filtered_count: 0, has_more: false } + }) + + await expect( + freshnessSubscribers.get('kanban')?.('old-version', 'new-version'), + ).rejects.toThrow('Failed to reload Kanban after unsafe incremental response') + }) + + it('rejects freshness when authoritative quick-search reconciliation fails', async () => { + performAdvancedSearch + .mockResolvedValueOnce({ jobs: [buildKanbanJob()] }) + .mockRejectedValueOnce(new Error('search unavailable')) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [buildKanbanJob({ name: 'Changed card' })], + removed_job_ids: [], + full_refresh_required: false, + }) + + const kanban = await mountHarness() + kanban.searchQuery.value = 'kick' + await kanban.handleSearch() + await vi.advanceTimersByTimeAsync(300) + await flushPromises() + + await expect( + freshnessSubscribers.get('kanban')?.('old-version', 'new-version'), + ).rejects.toThrow('search unavailable') + }) + + it('queues a forced freshness load behind an in-flight column request', async () => { + const kanban = await mountHarness() + const pendingLoad = deferred<{ + success: boolean + jobs: ReturnType[] + total: number + filtered_count: number + has_more: boolean + }>() + const reprioritizedJob = buildKanbanJob({ + priority: 200, + updated_at: '2026-05-15T00:00:00Z', + }) + getJobsByColumn.mockReset() + getJobsByColumn.mockReturnValueOnce(pendingLoad.promise).mockResolvedValueOnce({ + success: true, + jobs: [reprioritizedJob], + total: 1, + filtered_count: 1, + has_more: false, + }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [reprioritizedJob], + removed_job_ids: [], + full_refresh_required: false, + }) + + const ordinaryLoad = kanban.loadColumnJobs('in_progress', { force: true }) + const freshnessLoad = freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + await flushPromises() + + expect(getJobsByColumn).toHaveBeenCalledOnce() + + pendingLoad.resolve({ + success: true, + jobs: [buildKanbanJob()], + total: 1, + filtered_count: 1, + has_more: false, + }) + await ordinaryLoad + await freshnessLoad + + expect(getJobsByColumn).toHaveBeenCalledTimes(2) + expect(kanban.getJobsByStatus.value('in_progress')[0]?.priority).toBe(200) + }) + + it('runs an unsafe full refresh while a backend search is still loading', async () => { + const pendingSearch = deferred<{ jobs: ReturnType[] }>() + performAdvancedSearch + .mockReturnValueOnce(pendingSearch.promise) + .mockResolvedValueOnce({ jobs: [buildKanbanJob()] }) + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [], + removed_job_ids: [], + full_refresh_required: true, + }) + + const kanban = await mountHarness() + kanban.searchQuery.value = 'kick' + await kanban.handleSearch() + await vi.advanceTimersByTimeAsync(300) + await flushPromises() + expect(kanban.isLoading.value).toBe(true) + getJobsByColumn.mockClear() + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(getJobsByColumn.mock.calls.map(([columnId]) => columnId).sort()).toEqual([ + 'archived', + 'draft', + 'in_progress', + ]) + pendingSearch.resolve({ jobs: [buildKanbanJob()] }) + await flushPromises() + }) + + it('force reloads every visible column when incremental reconciliation is unsafe', async () => { + getKanbanChanges.mockResolvedValue({ + success: true, + jobs: [], + removed_job_ids: [], + full_refresh_required: true, + }) + await mountHarness() + getJobsByColumn.mockClear() + + await freshnessSubscribers.get('kanban')?.('old-version', 'new-version') + + expect(getJobsByColumn.mock.calls.map(([columnId]) => columnId).sort()).toEqual([ + 'archived', + 'draft', + 'in_progress', + ]) + }) + + it('force reloads every visible column when related display data changes', async () => { + await mountHarness() + getJobsByColumn.mockClear() + + await freshnessSubscribers.get('kanban_related')?.('old-version', 'new-version') + + expect(getJobsByColumn.mock.calls.map(([columnId]) => columnId).sort()).toEqual([ + 'archived', + 'draft', + 'in_progress', + ]) + }) + it('shows immediate local matches, then reconciles with backend results', async () => { performAdvancedSearch.mockResolvedValue({ jobs: [ @@ -621,6 +1078,101 @@ describe('useOptimizedKanban search reconciliation', () => { expect(reorderJob).toHaveBeenCalledWith('job-1', undefined, undefined, 'draft') }) + it('ignores a stale pre-drag search and applies the committed post-save search', async () => { + // Regression: a slow pre-drag search could restore the old status; resolving both + // generations proves the stale result is ignored and the post-save result wins. + const staleSearch = deferred<{ jobs: ReturnType[] }>() + const committedSearch = deferred<{ jobs: ReturnType[] }>() + const pendingReorder = deferred<{ success: boolean }>() + performAdvancedSearch + .mockReturnValueOnce(staleSearch.promise) + .mockReturnValueOnce(committedSearch.promise) + reorderJob.mockReturnValueOnce(pendingReorder.promise) + + const kanban = await mountHarness() + kanban.searchQuery.value = 'kick' + await kanban.handleSearch() + await vi.advanceTimersByTimeAsync(300) + await flushPromises() + + const reorderPromise = kanban.reorderJob('job-1', undefined, undefined, 'draft', 'drag-race') + + staleSearch.resolve({ + jobs: [ + buildKanbanJob({ + name: 'Stale before drag', + status: 'in_progress', + status_key: 'in_progress', + }), + ], + }) + await flushPromises() + + expect(kanban.filteredJobs.value).toMatchObject([ + { id: 'job-1', status: 'draft', status_key: 'draft' }, + ]) + + pendingReorder.resolve({ success: true }) + await reorderPromise + expect(performAdvancedSearch).toHaveBeenCalledTimes(2) + + committedSearch.resolve({ + jobs: [ + buildKanbanJob({ + name: 'Committed after drag', + status: 'draft', + status_key: 'draft', + }), + ], + }) + await flushPromises() + + expect(kanban.filteredJobs.value).toMatchObject([ + { + id: 'job-1', + name: 'Committed after drag', + status: 'draft', + status_key: 'draft', + }, + ]) + }) + + it('does not keep reorder pending while the post-save search is held', async () => { + // Regression: awaiting search reconciliation made drag persistence feel hung; holding + // that search proves the reorder promise settles independently. + const pendingReorder = deferred<{ success: boolean }>() + const postSaveSearch = deferred<{ jobs: ReturnType[] }>() + performAdvancedSearch + .mockResolvedValueOnce({ jobs: [buildKanbanJob()] }) + .mockReturnValueOnce(postSaveSearch.promise) + reorderJob.mockReturnValueOnce(pendingReorder.promise) + + const kanban = await mountHarness() + kanban.searchQuery.value = 'kick' + await kanban.handleSearch() + await vi.advanceTimersByTimeAsync(300) + await flushPromises() + + let reorderSettled = false + const reorderPromise = kanban + .reorderJob('job-1', undefined, undefined, 'draft', 'drag-background-search') + .then(() => { + reorderSettled = true + }) + + pendingReorder.resolve({ success: true }) + await flushPromises() + + expect(reorderSettled).toBe(true) + expect(performAdvancedSearch).toHaveBeenCalledTimes(2) + + postSaveSearch.resolve({ + jobs: [buildKanbanJob({ status: 'draft', status_key: 'draft' })], + }) + await reorderPromise + await flushPromises() + }) + it('does not force column revalidation after a successful reorder persistence', async () => { const kanban = await mountHarness() diff --git a/frontend/src/composables/__tests__/useWorkshopCalendarStoreIntegration.test.ts b/frontend/src/composables/__tests__/useWorkshopCalendarStoreIntegration.test.ts new file mode 100644 index 000000000..ef2269d4d --- /dev/null +++ b/frontend/src/composables/__tests__/useWorkshopCalendarStoreIntegration.test.ts @@ -0,0 +1,15 @@ +import { useCalendarStore } from '@kodeglot/vue-calendar' +import { createPinia, setActivePinia } from 'pinia' +import { beforeEach, describe, expect, it } from 'vitest' + +describe('workshop calendar store integration', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('uses the application Pinia instance', () => { + const calendarStore = useCalendarStore() + + expect(calendarStore.currentDate).toBeInstanceOf(Date) + }) +}) diff --git a/frontend/src/composables/useDataFreshness.ts b/frontend/src/composables/useDataFreshness.ts index 185b1a96f..a8afb017b 100644 --- a/frontend/src/composables/useDataFreshness.ts +++ b/frontend/src/composables/useDataFreshness.ts @@ -1,6 +1,6 @@ import { api } from '@/api/client' -type StaleCallback = () => void | Promise +type StaleCallback = (previousVersion: string, currentVersion: string) => void | Promise const knownVersions = new Map() const subscribers = new Map>() @@ -20,6 +20,9 @@ function subscribe(key: string, onStale: StaleCallback): () => void { bucket.add(onStale) return () => { bucket?.delete(onStale) + if (bucket && bucket.size === 0 && subscribers.get(key) === bucket) { + subscribers.delete(key) + } } } @@ -33,20 +36,35 @@ async function checkFreshness(): Promise { if (inFlight) return inFlight inFlight = (async () => { try { + const callbackErrors: unknown[] = [] const fresh = (await api.data_versions_retrieve()) as Record for (const [key, version] of Object.entries(fresh)) { const previous = knownVersions.get(key) - knownVersions.set(key, version) - if (previous === undefined || previous === version) continue + if (previous === undefined) { + knownVersions.set(key, version) + continue + } + if (previous === version) continue const bucket = subscribers.get(key) if (!bucket) continue + let callbacksSucceeded = true for (const cb of bucket) { try { - await cb() + await cb(previous, version) } catch (err) { - console.error(`useDataFreshness: stale callback for "${key}" threw`, err) + callbacksSucceeded = false + callbackErrors.push(err) } } + if (callbacksSucceeded) { + knownVersions.set(key, version) + } + } + if (callbackErrors.length === 1) { + throw callbackErrors[0] + } + if (callbackErrors.length > 1) { + throw new AggregateError(callbackErrors, 'Multiple data freshness subscribers failed') } } finally { inFlight = null diff --git a/frontend/src/composables/useJobFinancials.ts b/frontend/src/composables/useJobFinancials.ts deleted file mode 100644 index 5e9d22370..000000000 --- a/frontend/src/composables/useJobFinancials.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ref, type Ref } from 'vue' -import { api } from '../api/client' - -interface JobFinancials { - quoteTotal: number - actualTotal: number - invoiceTotal: number - toBeInvoiced: number -} - -export function useJobFinancials(jobId: Ref) { - const loading = ref(false) - const error = ref(null) - - const fetchJobFinancials = async (pricingMethodology?: string): Promise => { - loading.value = true - error.value = null - - try { - // Fetch both cost summary and invoices in parallel - const [costSummary, invoicesResponse] = await Promise.all([ - api.job_jobs_costs_summary_retrieve({ - params: { job_id: jobId.value }, - }), - api.job_jobs_invoices_retrieve({ - params: { job_id: jobId.value }, - }), - ]) - - // Extract amounts from cost summary - const quoteTotal = Number(costSummary.quote?.rev || 0) - const actualTotal = Number(costSummary.actual?.rev || 0) - - // Calculate invoice total from invoices - const invoices = invoicesResponse.invoices || [] - const invoiceTotal = invoices.reduce( - (sum: number, invoice: { total_excl_tax?: number }) => - sum + Number(invoice.total_excl_tax || 0), - 0, - ) - - // Calculate amount to invoice based on pricing methodology - const amountToInvoice = - pricingMethodology === 'fixed_price' && quoteTotal > 0 ? quoteTotal : actualTotal - - const toBeInvoiced = Math.max(0, amountToInvoice - invoiceTotal) - - return { - quoteTotal, - actualTotal, - invoiceTotal, - toBeInvoiced, - } - } catch (err) { - error.value = 'Failed to fetch job financial data' - console.error('Error fetching job financials:', err) - throw err - } finally { - loading.value = false - } - } - - return { - fetchJobFinancials, - loading: loading as Readonly>, - error: error as Readonly>, - } -} diff --git a/frontend/src/composables/useOptimizedKanban.ts b/frontend/src/composables/useOptimizedKanban.ts index 6bb0e0f9b..20d19ad90 100644 --- a/frontend/src/composables/useOptimizedKanban.ts +++ b/frontend/src/composables/useOptimizedKanban.ts @@ -1,4 +1,4 @@ -import { ref, computed, reactive, onMounted, watch } from 'vue' +import { ref, computed, reactive, onMounted, onUnmounted, watch } from 'vue' import { useRouter, useRoute } from 'vue-router' import { useDebounceFn } from '@vueuse/core' import { jobService } from '../services/job.service' @@ -41,6 +41,8 @@ interface KanbanMoveSnapshot { movedJob: KanbanJob | null } +type SearchMode = 'none' | 'quick' | 'advanced' + export function useOptimizedKanban(onJobsLoaded?: () => void) { const router = useRouter() const route = useRoute() @@ -61,14 +63,29 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { const activeStaffFilters = ref([]) const advancedFilters = ref({ ...DEFAULT_ADVANCED_FILTERS }) const filteredJobs = ref([]) + const searchMode = ref( + typeof initialQuery === 'string' && initialQuery.trim() ? 'quick' : 'none', + ) let latestSearchRequestId = 0 - let initialized = false + let loadingOperationCount = 0 + let kanbanLoadOperationCount = 0 let mounted = false let jobsLoadedCallbackPending = false let initialLoadPromise: Promise | null = null // Column-based state management const columnStates = reactive>({}) + const columnLoadQueues = new Map>() + + const beginLoadingOperation = (): void => { + loadingOperationCount += 1 + isLoading.value = true + } + + const endLoadingOperation = (): void => { + loadingOperationCount -= 1 + isLoading.value = loadingOperationCount > 0 + } // Watch URL for browser back/forward navigation watch( @@ -80,6 +97,7 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { if (parsed) { handleSearch() } else { + searchMode.value = 'none' filteredJobs.value = [] } } @@ -256,42 +274,54 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { const loadColumnJobs = async ( columnId: string, options: { force?: boolean } = {}, - ): Promise => { + ): Promise => { if (!columnStates[columnId]) { initializeColumnStates() } - const columnState = columnStates[columnId] - if (columnState.loading) return - - try { - columnState.loading = true - columnState.error = null + const performLoad = async (): Promise => { + const columnState = columnStates[columnId] + try { + columnState.loading = true + columnState.error = null - log(`Loading jobs for column: ${columnId}`) + log(`Loading jobs for column: ${columnId}`) - const data: KanbanColumnCacheEntry = await jobsStore.loadKanbanColumnWithCache( - columnId, - () => jobService.getJobsByColumn(columnId), - options, - ) - applyColumnData(columnId, data) + const data: KanbanColumnCacheEntry = await jobsStore.loadKanbanColumnWithCache( + columnId, + () => jobService.getJobsByColumn(columnId), + options, + ) + applyColumnData(columnId, data) - log(`Loaded ${data.jobIds.length} jobs for column: ${columnId}`) - } catch (err) { - columnState.error = err instanceof Error ? err.message : `Failed to load jobs for ${columnId}` - log(`Error loading jobs for column ${columnId}:`, err) + log(`Loaded ${data.jobIds.length} jobs for column: ${columnId}`) + return true + } catch (err) { + columnState.error = + err instanceof Error ? err.message : `Failed to load jobs for ${columnId}` + log(`Error loading jobs for column ${columnId}:`, err) + return false + } finally { + columnState.loading = false + } + } + const previousLoad = columnLoadQueues.get(columnId) + const queuedLoad = previousLoad ? previousLoad.then(performLoad) : performLoad() + columnLoadQueues.set(columnId, queuedLoad) + try { + return await queuedLoad } finally { - columnState.loading = false + if (columnLoadQueues.get(columnId) === queuedLoad) { + columnLoadQueues.delete(columnId) + } } } // Load all visible columns - const loadAllColumns = async (options: { force?: boolean } = {}): Promise => { - if (isLoading.value) return - + const loadAllColumns = async (options: { force?: boolean } = {}): Promise => { try { - isLoading.value = true + beginLoadingOperation() + kanbanLoadOperationCount += 1 error.value = null jobsStore.setCurrentContext('kanban') @@ -300,25 +330,134 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { const columns = KanbanCategorizationService.getAllColumns() // Load all columns in parallel - await Promise.all(columns.map((column) => loadColumnJobs(column.columnId, options))) + const outcomes = await Promise.all( + columns.map((column) => loadColumnJobs(column.columnId, options)), + ) + const succeeded = outcomes.every((outcome) => outcome) + if (!succeeded) { + error.value = 'Failed to load one or more Kanban columns' + return false + } notifyJobsLoaded() + return true } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to load kanban columns' log('Error loading kanban columns:', err) + return false } finally { - isLoading.value = false - jobsStore.setLoadingKanban(false) + endLoadingOperation() + kanbanLoadOperationCount -= 1 + jobsStore.setLoadingKanban(kanbanLoadOperationCount > 0) } } // Revalidate specific columns (for optimistic updates) - const revalidateColumns = async (columnIds: string[]): Promise => { + const revalidateColumns = async (columnIds: string[]): Promise => { log(`Revalidating columns: ${columnIds.join(', ')}`) - await Promise.all(columnIds.map((columnId) => loadColumnJobs(columnId, { force: true }))) + const outcomes = await Promise.all( + columnIds.map((columnId) => loadColumnJobs(columnId, { force: true })), + ) + return outcomes.every((outcome) => outcome) + } + + const replaceLocalKanbanJob = (job: KanbanJob): void => { + jobsStore.setKanbanJob(job) + Object.values(columnStates).forEach((columnState) => { + if (!columnState.jobs.some((currentJob) => currentJob.id === job.id)) { + return + } + columnState.jobs = columnState.jobs.map((currentJob) => + currentJob.id === job.id ? job : currentJob, + ) + }) + if (filteredJobs.value.some((currentJob) => currentJob.id === job.id)) { + filteredJobs.value = sortJobsForKanbanDisplay( + filteredJobs.value.map((currentJob) => (currentJob.id === job.id ? job : currentJob)), + ) + } + } + + const removeLocalKanbanJob = (jobId: string): void => { + jobsStore.removeKanbanJob(jobId) + Object.values(columnStates).forEach((columnState) => { + if (!columnState.jobs.some((job) => job.id === jobId)) { + return + } + columnState.jobs = columnState.jobs.filter((job) => job.id !== jobId) + }) + filteredJobs.value = filteredJobs.value.filter((job) => job.id !== jobId) + } + + const handleKanbanFreshnessChange = async (previousVersion: string): Promise => { + const response = await jobService.getKanbanChanges(previousVersion) + if (response.full_refresh_required) { + jobsStore.clearKanbanColumnCache() + const reloaded = await loadAllColumns({ force: true }) + if (!reloaded) { + throw new Error('Failed to reload Kanban after unsafe incremental response') + } + await refreshActiveSearch() + return + } + + const columnsToRevalidate = new Set() + for (const changedJob of response.jobs) { + const previousJob = + jobsStore.getKanbanJobById(changedJob.id) ?? + filteredJobs.value.find((job) => job.id === changedJob.id) ?? + null + + replaceLocalKanbanJob(changedJob) + if (!previousJob) { + columnsToRevalidate.add(KanbanCategorizationService.getColumnForStatus(changedJob.status)) + continue + } + if ( + previousJob.status === changedJob.status && + previousJob.priority === changedJob.priority + ) { + continue + } + + columnsToRevalidate.add(KanbanCategorizationService.getColumnForStatus(previousJob.status)) + columnsToRevalidate.add(KanbanCategorizationService.getColumnForStatus(changedJob.status)) + } + + for (const removedJobId of response.removed_job_ids) { + const previousJob = + jobsStore.getKanbanJobById(removedJobId) ?? + filteredJobs.value.find((job) => job.id === removedJobId) ?? + null + removeLocalKanbanJob(removedJobId) + if (previousJob) { + columnsToRevalidate.add(KanbanCategorizationService.getColumnForStatus(previousJob.status)) + } + } + + if (columnsToRevalidate.size > 0) { + const revalidated = await revalidateColumns([...columnsToRevalidate]) + if (!revalidated) { + throw new Error('Failed to revalidate changed Kanban columns') + } + } + await refreshActiveSearch() } + const unsubscribeKanbanFreshness = dataFreshness.subscribe('kanban', handleKanbanFreshnessChange) + const unsubscribeKanbanRelatedFreshness = dataFreshness.subscribe('kanban_related', async () => { + const reloaded = await loadAllColumns({ force: true }) + if (!reloaded) { + throw new Error('Failed to reload Kanban related display data') + } + await refreshActiveSearch() + }) + onUnmounted(() => { + unsubscribeKanbanFreshness() + unsubscribeKanbanRelatedFreshness() + }) + const getColumnJobIds = (columnId: string): string[] => (columnStates[columnId]?.jobs ?? []).map((job) => job.id) @@ -419,8 +558,7 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { // Get jobs for a specific column with staff filtering const getJobsByStatus = computed(() => (columnId: string) => { - // Show filtered results when: we have results, there's a search query, OR we're loading a search - if (filteredJobs.value.length > 0 || searchQuery.value.trim() !== '' || isLoading.value) { + if (searchMode.value !== 'none') { // When searching, group filteredJobs by column const grouped: Record = {} filteredJobs.value.forEach((job) => { @@ -469,11 +607,7 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { // Whether search/filter is active const isSearchActive = computed(() => { - return ( - filteredJobs.value.length > 0 || - searchQuery.value.trim() !== '' || - activeStaffFilters.value.length > 0 - ) + return searchMode.value !== 'none' || activeStaffFilters.value.length > 0 }) const insertJobInColumn = ( @@ -734,6 +868,10 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { status, }) + if (searchMode.value !== 'none') { + latestSearchRequestId += 1 + } + const targetStatus = status const targetColumnId = targetStatus ? KanbanCategorizationService.getColumnForStatus(targetStatus) @@ -842,6 +980,14 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { }) await revalidateColumns(columnIds) } + } finally { + void refreshActiveSearch().catch((searchError: unknown) => { + log('kanban.drag.search-reconciliation.error', { + dragId, + jobId, + error: searchError instanceof Error ? searchError.message : String(searchError), + }) + }) } } @@ -916,13 +1062,21 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { } } - const performBackendSearch = async (query: string, requestId: number): Promise => { - if (requestId !== latestSearchRequestId || searchQuery.value.trim() !== query) { + const performBackendSearch = async ( + query: string, + requestId: number, + fallbackLocally: boolean, + ): Promise => { + if ( + requestId !== latestSearchRequestId || + searchMode.value !== 'quick' || + searchQuery.value.trim() !== query + ) { return } try { - isLoading.value = true + beginLoadingOperation() const searchFilters: AdvancedFilters = { ...DEFAULT_ADVANCED_FILTERS, @@ -930,7 +1084,11 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { } const response: AdvancedSearchResponse = await jobService.performAdvancedSearch(searchFilters) - if (requestId !== latestSearchRequestId || searchQuery.value.trim() !== query) { + if ( + requestId !== latestSearchRequestId || + searchMode.value !== 'quick' || + searchQuery.value.trim() !== query + ) { return } @@ -948,20 +1106,25 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { renderedColumnOrder: summarizeFilteredColumnOrder(filteredJobs.value), }) } catch (err) { - if (requestId !== latestSearchRequestId || searchQuery.value.trim() !== query) { + if ( + requestId !== latestSearchRequestId || + searchMode.value !== 'quick' || + searchQuery.value.trim() !== query + ) { return } log('Error performing search:', err) + if (!fallbackLocally) { + throw err + } filteredJobs.value = searchJobsLocally(getAllLoadedJobs(), query) } finally { - if (requestId === latestSearchRequestId) { - isLoading.value = false - } + endLoadingOperation() } } const debouncedBackendSearch = useDebounceFn((query: string, requestId: number) => { - return performBackendSearch(query, requestId) + return performBackendSearch(query, requestId, true) }, 300) // Search functionality - show immediate local substring matches, then @@ -972,11 +1135,12 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { const requestId = latestSearchRequestId if (!trimmedQuery) { + searchMode.value = 'none' filteredJobs.value = [] - isLoading.value = false return } + searchMode.value = 'quick' filteredJobs.value = searchJobsLocally(getAllLoadedJobs(), trimmedQuery) debouncedBackendSearch(trimmedQuery, requestId) @@ -987,22 +1151,16 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { ) } - // Advanced search - const handleAdvancedSearch = async (): Promise => { + const performAdvancedSearchRequest = async ( + filters: AdvancedFilters, + requestId: number, + ): Promise => { try { - isLoading.value = true - filteredJobs.value = [] - - // Convert array fields to comma-separated strings for backend - const backendFilters = { - ...advancedFilters.value, - status: Array.isArray(advancedFilters.value.status) - ? advancedFilters.value.status.join(',') - : advancedFilters.value.status, - } as unknown as AdvancedFilters - - const response: AdvancedSearchResponse = - await jobService.performAdvancedSearch(backendFilters) + beginLoadingOperation() + const response: AdvancedSearchResponse = await jobService.performAdvancedSearch(filters) + if (requestId !== latestSearchRequestId || searchMode.value !== 'advanced') { + return + } if (!response.jobs) { throw new Error( `Advanced search response missing jobs: ${response.error ?? 'no error given'}`, @@ -1010,22 +1168,66 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { } filteredJobs.value = response.jobs } catch (err) { - error.value = err instanceof Error ? err.message : 'Failed to perform advanced search' - log('Error performing advanced search:', err) - filteredJobs.value = [] + if (requestId !== latestSearchRequestId || searchMode.value !== 'advanced') { + return + } throw err } finally { - isLoading.value = false + endLoadingOperation() } } + // Advanced search + const handleAdvancedSearch = async (): Promise => { + latestSearchRequestId += 1 + const requestId = latestSearchRequestId + searchMode.value = 'advanced' + filteredJobs.value = [] + + try { + await performAdvancedSearchRequest(advancedFilters.value, requestId) + } catch (err) { + if (requestId === latestSearchRequestId && searchMode.value === 'advanced') { + error.value = err instanceof Error ? err.message : 'Failed to perform advanced search' + log('Error performing advanced search:', err) + filteredJobs.value = [] + } + throw err + } + } + + const refreshActiveSearch = async (): Promise => { + if (searchMode.value === 'none') { + return + } + + latestSearchRequestId += 1 + const requestId = latestSearchRequestId + if (searchMode.value === 'quick') { + const query = searchQuery.value.trim() + if (!query) { + searchMode.value = 'none' + filteredJobs.value = [] + return + } + await performBackendSearch(query, requestId, false) + return + } + + await performAdvancedSearchRequest(advancedFilters.value, requestId) + } + // Utility functions const clearFilters = (): void => { + latestSearchRequestId += 1 + searchMode.value = 'none' advancedFilters.value = { ...DEFAULT_ADVANCED_FILTERS } filteredJobs.value = [] } const backToKanban = (): void => { + latestSearchRequestId += 1 + searchMode.value = 'none' searchQuery.value = '' filteredJobs.value = [] const newQuery = { ...route.query } @@ -1078,14 +1280,6 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { activeStaffFilters.value = staffIds } - watch( - () => jobsStore.kanbanCacheGeneration, - async (newGeneration, oldGeneration) => { - if (!initialized || newGeneration === oldGeneration) return - await loadAllColumns({ force: true }) - }, - ) - const startInitialKanbanLoad = (): Promise => { if (initialLoadPromise) { return initialLoadPromise @@ -1101,11 +1295,9 @@ export function useOptimizedKanban(onJobsLoaded?: () => void) { if (hydratedFromCache) { notifyJobsLoaded() await loadStatusChoices() - initialized = true checkFreshnessInBackground() } else { await Promise.all([loadAllColumns(), loadStatusChoices()]) - initialized = true checkFreshnessInBackground() } diff --git a/frontend/src/constants/job-tabs.ts b/frontend/src/constants/job-tabs.ts index ce19afadf..b6403253f 100644 --- a/frontend/src/constants/job-tabs.ts +++ b/frontend/src/constants/job-tabs.ts @@ -9,7 +9,7 @@ export const JobTabsSchema = z.enum([ 'quote', 'actual', 'financial', - 'costAnalysis', + 'finishJob', 'jobSettings', 'history', 'attachments', diff --git a/frontend/src/pages/crm/people/[id].vue b/frontend/src/pages/crm/people/[id].vue index 8daadb78c..4a59a5d03 100644 --- a/frontend/src/pages/crm/people/[id].vue +++ b/frontend/src/pages/crm/people/[id].vue @@ -350,7 +350,7 @@ async function saveMethod(): Promise { const body = { method_type: methodType.value, value: methodValue.value.trim(), - label: methodLabel.value.trim(), + label: methodLabel.value.trim() || null, is_primary: methodPrimary.value, } try { diff --git a/frontend/src/pages/jobs/[id]/(index).vue b/frontend/src/pages/jobs/[id]/(index).vue index 97f0806ab..1969d2690 100644 --- a/frontend/src/pages/jobs/[id]/(index).vue +++ b/frontend/src/pages/jobs/[id]/(index).vue @@ -313,7 +313,6 @@ import { useRoute, useRouter } from 'vue-router' import { useJobsStore } from '@/stores/jobs' import { useJobTabs } from '@/composables/useJobTabs' import { useJobHeaderAutosave } from '@/composables/useJobHeaderAutosave' -import { useJobFinancials } from '@/composables/useJobFinancials' import { useCompanyDefaultsStore } from '@/stores/companyDefaults' import { api } from '@/api/client' import { ArrowLeft, Printer } from 'lucide-vue-next' @@ -450,7 +449,6 @@ const handlePricingMethodologyUpdate = (newMethod: string) => { } // Initialize the job financials composable -const { fetchJobFinancials } = useJobFinancials(jobId) const handleRejectedChange = async () => { if (!jobHeader.value) return @@ -484,14 +482,16 @@ const handleRejectedChange = async () => { } else { // CHECKING - validate no money to be invoiced try { - // Use the composable to fetch financial data - const financials = await fetchJobFinancials(jobHeader.value.pricing_methodology || undefined) + // The server owns this figure; it excludes voided invoices and respects + // the price cap, neither of which the old local calculation did. + const { summary } = await api.job_jobs_finish_retrieve({ + params: { job_id: jobId.value }, + }) - // Check if there's money to be invoiced - if (financials.toBeInvoiced > 0) { + if (summary.remaining_to_invoice_excl_gst > 0) { // Show warning but allow proceeding const confirmed = confirm( - `Warning: Job has ${formatCurrency(financials.toBeInvoiced)} still to be invoiced. Are you sure you want to reject?`, + `Warning: Job has ${formatCurrency(summary.remaining_to_invoice_excl_gst)} still to be invoiced. Are you sure you want to reject?`, ) if (!confirmed) { diff --git a/frontend/src/pages/reports/data-quality/archived-jobs.vue b/frontend/src/pages/reports/data-quality/archived-jobs.vue index 289a53acf..5f1341289 100644 --- a/frontend/src/pages/reports/data-quality/archived-jobs.vue +++ b/frontend/src/pages/reports/data-quality/archived-jobs.vue @@ -287,11 +287,7 @@ interface ValidationIssue { jobNumber: string companyName: string type: - | 'open_timesheets' - | 'incomplete_costs' - | 'missing_invoices' - | 'invalid_status' - | 'open_tasks' + 'open_timesheets' | 'incomplete_costs' | 'missing_invoices' | 'invalid_status' | 'open_tasks' details: string archivedDate: string jobValue: number diff --git a/frontend/src/pages/reports/data-quality/duplicate-identities.vue b/frontend/src/pages/reports/data-quality/duplicate-identities.vue index 4f3717cfa..da37429c1 100644 --- a/frontend/src/pages/reports/data-quality/duplicate-identities.vue +++ b/frontend/src/pages/reports/data-quality/duplicate-identities.vue @@ -171,8 +171,7 @@ type PersonGroup = z.infer type Summary = z.infer type GroupItem = - | { entityKind: 'company'; group: CompanyGroup } - | { entityKind: 'person'; group: PersonGroup } + { entityKind: 'company'; group: CompanyGroup } | { entityKind: 'person'; group: PersonGroup } const EMPTY_SUMMARY: Summary = { company_merge_groups: 0, diff --git a/frontend/src/pages/reports/rdti-spend.vue b/frontend/src/pages/reports/rdti-spend.vue index 6a4814631..30c992697 100644 --- a/frontend/src/pages/reports/rdti-spend.vue +++ b/frontend/src/pages/reports/rdti-spend.vue @@ -234,12 +234,12 @@ - {{ job.rdti_type === 'core' ? 'Core' : 'Supporting' }} + {{ job.rdti_type === 'core_rd' ? 'Core' : 'Supporting' }}
    - @@ -78,7 +77,6 @@ - - @@ -154,7 +152,6 @@ const filteredStaff = computed(() => s.is_superuser, s.groups, s.user_permissions, - s.last_login, s.date_joined, ] .map((v) => String(v ?? '')) diff --git a/frontend/src/views/purchasing/ItemSelect.vue b/frontend/src/views/purchasing/ItemSelect.vue index 396c45180..c75c89ef3 100644 --- a/frontend/src/views/purchasing/ItemSelect.vue +++ b/frontend/src/views/purchasing/ItemSelect.vue @@ -185,9 +185,7 @@ const displayPrice = (item: DisplayItem) => { function variantSignature(item: DisplayItem): string { if (isLabourItem(item)) return '' const stock = item as StockItem - const metalType = stock.metal_type && stock.metal_type !== 'unspecified' ? stock.metal_type : null - - return [metalType, stock.alloy, stock.specifics] + return [stock.metal_type, stock.alloy, stock.specifics] .map((part) => (part ?? '').toString().trim()) .filter(Boolean) .join(' · ') diff --git a/frontend/src/views/purchasing/__tests__/ItemSelect.test.ts b/frontend/src/views/purchasing/__tests__/ItemSelect.test.ts index 268e76d6b..d75c0d49b 100644 --- a/frontend/src/views/purchasing/__tests__/ItemSelect.test.ts +++ b/frontend/src/views/purchasing/__tests__/ItemSelect.test.ts @@ -556,7 +556,7 @@ describe('ItemSelect server-side search and rendering', () => { vi.useRealTimers() }) - it('suppresses the noisy unspecified metal_type in the metadata line', async () => { + it('omits an unset metal_type from the metadata line', async () => { vi.useFakeTimers() const store = useStockStore() store.items = [] @@ -564,7 +564,7 @@ describe('ItemSelect server-side search and rendering', () => { results: [ buildStockItem({ id: 's1', - metal_type: 'unspecified', + metal_type: null, alloy: '316', specifics: '1.5mm sheet', times_used: 4, @@ -590,7 +590,6 @@ describe('ItemSelect server-side search and rendering', () => { const signature = stockOption.find('[data-automation-id="ItemSelect-variant-signature"]') expect(signature.exists()).toBe(true) expect(signature.text()).toBe('316 · 1.5mm sheet') - expect(stockOption.text()).not.toContain('unspecified') vi.useRealTimers() }) diff --git a/frontend/tests/job/edit-job-settings.spec.ts b/frontend/tests/job/edit-job-settings.spec.ts index f3db35608..84340ac5b 100644 --- a/frontend/tests/job/edit-job-settings.spec.ts +++ b/frontend/tests/job/edit-job-settings.spec.ts @@ -1,4 +1,5 @@ import debug from 'debug' +import { schemas } from '@/api/generated/api' import { test, expect } from '../fixtures/auth' import { getCompanyDefaults } from '../fixtures/api' import { @@ -12,6 +13,12 @@ import { const log = debug('e2e:job') +const getJobIdFromUrl = (url: string): string => { + const match = new URL(url).pathname.match(/\/jobs\/([0-9a-f-]{36})/) + if (!match) throw new Error(`Could not extract job id from ${url}`) + return match[1] +} + const EDIT_JOB_BUDGET_MS = { navigateSettingsTab: 2000, autosave: 1500, @@ -574,6 +581,8 @@ test.describe.serial('edit job', () => { authenticatedPage: page, }) => { const jobUrl = await createTestJob(page, 'Delete Person') + const jobId = getJobIdFromUrl(jobUrl) + const headerUrl = `/api/job/jobs/${jobId}/header/` await page.goto(jobUrl) await page.waitForLoadState('networkidle') @@ -599,6 +608,22 @@ test.describe.serial('edit job', () => { }) }) + // A previous direct person-save path advanced Job.updated_at without refreshing + // the browser's OCC token. Waiting for authoritative selection here makes the + // subsequent archive exercise the real select-then-clear regression. + await expect + .poll( + async () => { + const response = await page.request.get(headerUrl) + if (!response.ok()) { + throw new Error(`Job header read failed: ${response.status()} ${await response.text()}`) + } + return schemas.JobHeaderResponse.parse(await response.json()).person_name + }, + { timeout: 10000 }, + ) + .toBe(personName) + await expectStepUnder( 'reopen modal and delete the person', EDIT_JOB_BUDGET_MS.createOrSwitchPerson, @@ -628,6 +653,30 @@ test.describe.serial('edit job', () => { .filter({ hasText: personName }), ).toHaveCount(0, { timeout: 10000 }) }) + + await test.step('verify the archived person is cleared from the job', async () => { + await expect + .poll( + async () => { + const response = await page.request.get(headerUrl) + if (!response.ok()) { + throw new Error( + `Job header read failed: ${response.status()} ${await response.text()}`, + ) + } + return schemas.JobHeaderResponse.parse(await response.json()).person_id + }, + { timeout: 10000 }, + ) + .toBeNull() + + await page.reload() + await autoId(page, 'JobViewTabs-jobSettings').click() + await autoId(page, 'PersonSelector-display').waitFor({ timeout: 10000 }) + await waitForSettingsInitialized(page) + await page.waitForLoadState('networkidle') + await expect(autoId(page, 'PersonSelector-display')).toHaveValue('') + }) }) test('change company', async ({ authenticatedPage: page }) => { diff --git a/frontend/tests/job/job-xero-invoice.spec.ts b/frontend/tests/job/job-xero-invoice.spec.ts index 63e9da3d9..5cc818a9d 100644 --- a/frontend/tests/job/job-xero-invoice.spec.ts +++ b/frontend/tests/job/job-xero-invoice.spec.ts @@ -12,7 +12,7 @@ const getJobIdFromUrl = (url: string): string => { test.describe('job xero invoice', () => { test.setTimeout(120000) - test('create invoice in Xero from Actual tab', async ({ + test('invoice a job from Finish Job and see the balance settle', async ({ authenticatedPage: page, sharedEditJobUrl, }) => { @@ -21,12 +21,18 @@ test.describe('job xero invoice', () => { await page.goto(sharedEditJobUrl) await page.waitForLoadState('networkidle') - await autoId(page, 'JobViewTabs-actual').click() + await autoId(page, 'JobViewTabs-finishJob').click() - const invoiceItems = page.locator('ul[role="list"] li') + // The balance is server-owned; the tab must render it before offering to invoice. + await expect(autoId(page, 'JobFinishTab-total-to-pay')).toBeVisible({ timeout: 10000 }) + const remaining = autoId(page, 'JobFinishTab-remaining-excl-gst') + await expect(remaining).toBeVisible() + await expect(remaining).not.toHaveText(/\$0\.00/) + + const invoiceItems = autoId(page, 'JobInvoiceCard-list').locator('li') const initialCount = await invoiceItems.count() - const createInvoiceButton = page.getByRole('button', { name: /Create .*Invoice/ }) + const createInvoiceButton = autoId(page, 'JobFinishTab-create-invoice') await expect(createInvoiceButton).toBeVisible({ timeout: 10000 }) const responsePromise = page.waitForResponse( @@ -41,12 +47,9 @@ test.describe('job xero invoice', () => { await createInvoiceButton.click() - // Modal should appear — select the primary invoice mode - const invoiceRemainingButton = page.getByRole('button', { - name: /Invoice remaining quote/, - }) - await expect(invoiceRemainingButton).toBeVisible({ timeout: 10000 }) - await invoiceRemainingButton.click() + const invoiceFullButton = autoId(page, 'JobFinishTab-mode-invoice-full') + await expect(invoiceFullButton).toBeVisible({ timeout: 10000 }) + await invoiceFullButton.click() const response = await responsePromise if (!response.ok()) { @@ -57,5 +60,11 @@ test.describe('job xero invoice', () => { } await expect(invoiceItems).toHaveCount(initialCount + 1, { timeout: 20000 }) + + // KAN-323: a fully invoiced job shows nothing remaining and stops asking for + // another invoice, rather than offering a ceremonial $0 one. + await expect(remaining).toHaveText(/\$0\.00/, { timeout: 20000 }) + await expect(autoId(page, 'JobFinishTab-fully-invoiced')).toBeVisible() + await expect(createInvoiceButton).toHaveCount(0) }) }) diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 64d5cc445..51f22c9f9 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -1,4396 +1,2724 @@ -apps/job/services/kanban_categorization_service.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/job/services/kanban_categorization_service.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/tests/fixtures/xero_responses.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_helpers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_helpers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] -apps/workflow/api/reports/utils.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/services/validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/validation.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/quoting/services/providers/common.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/delta_checksum.py:0: error: Incompatible types in assignment (expression has type "list[str]", variable has type "Decimal") [assignment] -apps/job/services/delta_checksum.py:0: error: Argument 1 to "join" of "str" has incompatible type "Decimal"; expected "Iterable[str]" [arg-type] -apps/accounts/staff_anonymization.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounts/staff_anonymization.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/company/utils.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/services/geocoding_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delta_checksum.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delta_checksum.py:0: note: Use "-> None" if function does not return a value -apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/services/providers/mistral_provider.py:0: error: Argument 1 to "_parse_price_from_string" of "MistralPriceExtractionProvider" has incompatible type "str | None"; expected "str" [arg-type] -apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "encode_pdf" in typed context [no-untyped-call] -apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] -apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] -apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] -apps/quoting/tests/test_ocr_fixtures.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_ocr_fixtures.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/tests/test_ocr_fixtures.py:0: error: Call to untyped function "_ocr_response" in typed context [no-untyped-call] -apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] -apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.InvoicePayload.date" is not valid as a type [valid-type] -apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases -apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] -apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.QuotePayload.date" is not valid as a type [valid-type] -apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases -apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] -apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.POPayload.date" is not valid as a type [valid-type] -apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases -apps/workflow/accounting/types.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/types.py:0: error: Missing type arguments for generic type "dict" [type-arg] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "rstrip" [union-attr] -docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] -docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] -docketworks/settings.py:0: error: Function is missing a return type annotation [no-untyped-def] -docketworks/settings.py:0: note: Use "-> None" if function does not return a value -docketworks/settings.py:0: error: Function is missing a type annotation [no-untyped-def] -docketworks/settings.py:0: error: Call to untyped function "configure_site_for_environment" in typed context [no-untyped-call] -apps/workflow/models/service_api_key.py:0: error: Call to untyped function "generate_api_key" of "ServiceAPIKey" in typed context [no-untyped-call] -apps/workflow/models/service_api_key.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/job/models/spreadsheet.py:0: error: Item "None" of "Job | None" has no attribute "job_number" [union-attr] -apps/accounting/models/invoice.py:0: error: Incompatible return value type (got "int | Decimal", expected "Decimal") [return-value] -apps/accounting/models/invoice.py:0: error: Generator has incompatible item type "Any | Decimal"; expected "bool" [misc] apps/accounting/models/invoice.py:0: error: "BaseXeroInvoiceDocument" has no attribute "line_items" [attr-defined] -apps/job/helpers.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/company/models.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] -apps/company/models.py:0: error: Incompatible type for lookup 'company_id': (got "UUID | None", expected "UUID | str") [misc] -apps/company/models.py:0: error: Need type annotation for "SUPPLIERPICKUPADDRESS_INTERNAL_FIELDS" (hint: "SUPPLIERPICKUPADDRESS_INTERNAL_FIELDS: list[] = ...") [var-annotated] -apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/file_service.py:0: error: Incompatible types in assignment (expression has type "Image", variable has type "ImageFile") [assignment] -apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/file_service.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] -apps/job/services/file_service.py:0: error: Call to untyped function "create_thumbnail" in typed context [no-untyped-call] -apps/job/models/costing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/models/costing.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/costing.py:0: error: "type[Model]" has no attribute "objects" [attr-defined] -apps/job/models/costing.py:0: error: Call to untyped function "_save_with_summary_update" in typed context [no-untyped-call] +apps/accounting/models/invoice.py:0: error: Generator has incompatible item type "Any | Decimal"; expected "bool" [misc] +apps/accounting/models/invoice.py:0: error: Incompatible return value type (got "int | Decimal", expected "Decimal") [return-value] +apps/accounting/serializers/rdti_spend_serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] +apps/accounting/services/core.py:0: error: "CostLine" has no attribute "is_billable_meta" [attr-defined] +apps/accounting/services/core.py:0: error: Argument "key" to "max" has incompatible type "Callable[[dict[str, object]], object]"; expected "Callable[[dict[str, object]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] +apps/accounting/services/core.py:0: error: Argument 1 to "_calculate_job_breakdown" of "StaffPerformanceService" has incompatible type "list[CostLine]"; expected "QuerySet[Any, Any]" [arg-type] +apps/accounting/services/core.py:0: error: Argument 1 to "localtime" has incompatible type "object"; expected "datetime | None" [arg-type] +apps/accounting/services/core.py:0: error: Argument 2 to "_get_color" of "KPIService" has incompatible type "Decimal"; expected "float" [arg-type] +apps/accounting/services/core.py:0: error: Generator has incompatible item type "Decimal"; expected "bool" [misc] +apps/accounting/services/core.py:0: error: Incompatible return value type (got "None", expected "dict[str, Any]") [return-value] +apps/accounting/services/core.py:0: error: Incompatible return value type (got "object", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "CostLine", variable has type "CostLine@AnnotatedWith[TypedDict({'is_billable': str})]") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "CostLine", variable has type "CostLine@AnnotatedWith[TypedDict({'is_billable': str})]") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "Decimal") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "object", variable has type "date") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] +apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/accounting/services/core.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] +apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] +apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] +apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] +apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] +apps/accounting/services/core.py:0: error: Unsupported left operand type for / ("str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("int" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("int" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "int") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "int") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("float" and "Decimal") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("float" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for / ("float" and "str") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for / ("str" and "float") [operator] +apps/accounting/services/core.py:0: error: Unsupported operand types for > ("str" and "int") [operator] +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Both left and right operands are unions +apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" +apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" +apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" +apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" +apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" +apps/accounting/services/invoice_calculation.py:0: error: Returning Any from function declared to return "Job" [no-any-return] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] +apps/accounting/services/payroll_reconciliation_service.py:0: error: Key expression in dictionary comprehension has incompatible type "str | None"; expected type "str" [misc] +apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] +apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] +apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] +apps/accounting/services/sales_pipeline_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/accounting/services/sales_pipeline_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounting/services/sales_pipeline_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounting/services/wip_service.py:0: error: Argument "key" to "sorted" has incompatible type "Callable[[dict[str, object]], object]"; expected "Callable[[dict[str, object]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] +apps/accounting/services/wip_service.py:0: error: Incompatible return value type (got "object", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] +apps/accounting/tests/test_core_nplusone.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounting/tests/test_core_nplusone.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] +apps/accounting/tests/test_core_nplusone.py:0: error: Returning Any from function declared to return "Job" [no-any-return] +apps/accounting/tests/test_sales_pipeline_api.py:0: error: "SalesPipelineAPITests" has no attribute "office_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_api.py:0: error: "type[SalesPipelineAPITests]" has no attribute "office_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_api.py:0: error: Call to untyped function "setUpTestData" of "BaseAPITestCase" in typed context [no-untyped-call] +apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounting/tests/test_sales_pipeline_api.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] +apps/accounting/views/kpi_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/accounting/views/sales_forecast_view.py:0: error: Argument "row_date" to "build_row" has incompatible type "Any | None"; expected "str" [arg-type] +apps/accounting/views/sales_forecast_view.py:0: error: Argument 1 to "_get_total_invoiced_for_job" of "SalesForecastMonthDetailAPIView" has incompatible type "Job | None"; expected "Job" [arg-type] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Company | Any | None" has no attribute "name" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "company" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "job_number" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "latest_actual" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] +apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "start_date" [union-attr] apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] -apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "EmailField[str | Combinable, str]", variable has type "str") [assignment] -apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable, str]", variable has type "str") [assignment] -apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable, str]", variable has type "str") [assignment] -apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable | None, str | None]", variable has type "str | None") [assignment] apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] +apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable | None, str | None]", variable has type "str | None") [assignment] +apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable, str]", variable has type "str") [assignment] +apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "CharField[str | int | Combinable, str]", variable has type "str") [assignment] apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "DateTimeField[str | datetime | date | Combinable, datetime]", variable has type "datetime") [assignment] -apps/job/models/job_file.py:0: error: Argument 1 to "get_job_folder_path" has incompatible type "int"; expected "str" [arg-type] -apps/job/models/job_file.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] -apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/models/job_event.py:0: error: Call to untyped function (unknown) in typed context [no-untyped-call] -apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] +apps/accounts/models.py:0: error: Incompatible types in assignment (expression has type "EmailField[str | Combinable, str]", variable has type "str") [assignment] +apps/accounts/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/accounts/staff_anonymization.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounts/staff_anonymization.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounts/tests/test_auth_observability.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] +apps/accounts/tests/test_auth_observability.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] +apps/accounts/utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/utils.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] +apps/accounts/utils.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/views/staff_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/staff_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/staff_api.py:0: error: Missing type arguments for generic type "ListCreateAPIView" [type-arg] +apps/accounts/views/staff_views.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] +apps/accounts/views/staff_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/views/staff_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/accounts/views/staff_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/staff_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/staff_views.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] +apps/accounts/views/token_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/token_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] +apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] +apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] +apps/accounts/views/token_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounts/views/token_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/accounts/views/user_profile_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/company/management/commands/merge_companies.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/management/commands/merge_companies.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/models.py:0: error: Incompatible type for lookup 'company_id': (got "UUID | None", expected "UUID | str") [misc] +apps/company/models.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] +apps/company/models.py:0: error: Need type annotation for "SUPPLIERPICKUPADDRESS_INTERNAL_FIELDS" (hint: "SUPPLIERPICKUPADDRESS_INTERNAL_FIELDS: list[] = ...") [var-annotated] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] +apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] +apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] +apps/company/services/company_rest_service.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] +apps/company/services/company_rest_service.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] +apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/services/company_rest_service.py:0: error: Value of type "list[dict[str, Any]] | int | Any | str | None" is not indexable [index] +apps/company/services/geocoding_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/tests/test_company_fts_search.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Staff | AnonymousUser") [assignment] +apps/company/tests/test_company_invoice_summary.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] +apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/tests/test_company_merge_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/company/tests/test_company_merge_service.py:0: error: Value of type "Any | None" is not indexable [index] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "get_company_for_xero" in typed context [no-untyped-call] +apps/company/utils.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/views/company_rest_views.py:0: error: Argument 1 to "get_company_by_id" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] +apps/company/views/company_rest_views.py:0: error: Argument 1 to "get_company_jobs" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] +apps/company/views/company_rest_views.py:0: error: Argument 1 to "update_company" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] +apps/company/views/company_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/views/company_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/views/company_rest_views.py:0: error: Incompatible types in assignment (expression has type "CompanyDuplicateErrorResponseSerializer", variable has type "CompanyErrorResponseSerializer") [assignment] +apps/company/views/company_rest_views.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/company/views/contact_method_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/views/contact_method_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/views/contact_method_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] +apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/company/views/supplier_pickup_address_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] +apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/crm/services/phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/crm/services/phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] +apps/crm/tests/test_phone_call_service.py:0: error: Cannot assign to a method [method-assign] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] +apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] +apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] +apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] +apps/job/diff.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/diff.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/diff.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/job/diff.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/job/helpers.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/importers/google_sheets.py:0: error: Call to untyped function "from_service_account_file" of "Credentials" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: "object" has no attribute "update" [attr-defined] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_labour_column" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_labour_column" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_material_columns" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_material_columns" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "find_validation_cells" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "find_validation_cells" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "validate_totals" in typed context [no-untyped-call] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/importers/quote_spreadsheet.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "ValidationError") [assignment] +apps/job/importers/quote_spreadsheet.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/importers/quote_spreadsheet.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "conflicts" (hint: "conflicts: list[] = ...") [var-annotated] +apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "errors" (hint: "errors: list[] = ...") [var-annotated] +apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "warnings" (hint: "warnings: list[] = ...") [var-annotated] +apps/job/importers/quote_spreadsheet.py:0: error: Unsupported target for indexed assignment ("object") [index] +apps/job/importers/quote_spreadsheet.py:0: error: Unsupported target for indexed assignment ("object") [index] +apps/job/management/commands/set_paid_flag_jobs.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/management/commands/set_paid_flag_jobs.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/management/commands/test_gemini_chat.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/management/commands/test_gemini_chat.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/mixins.py:0: error: Call to untyped function "get_job_by_number" in typed context [no-untyped-call] +apps/job/mixins.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/mixins.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] +apps/job/models/costing.py:0: error: "type[Model]" has no attribute "objects" [attr-defined] +apps/job/models/costing.py:0: error: Call to untyped function "_save_with_summary_update" in typed context [no-untyped-call] +apps/job/models/costing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/models/costing.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] +apps/job/models/job.py:0: error: Call to untyped function "_apply_change_side_effects" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_clear_assigned_staff_on_archive" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_detect_field_changes" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_infer_event_type" of "Job" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Call to untyped function "_record_change_event" in typed context [no-untyped-call] +apps/job/models/job.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] +apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] +apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] +apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "DateTimeField[str | datetime | date | Combinable | None, datetime | None]", variable has type "datetime | None") [assignment] +apps/job/models/job.py:0: error: Item "ForeignObjectRel" of "Field[Any, Any] | ForeignObjectRel" has no attribute "verbose_name" [union-attr] +apps/job/models/job.py:0: error: Item "None" of "str | None" has no attribute "rstrip" [union-attr] +apps/job/models/job.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] +apps/job/models/job.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] +apps/job/models/job.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] +apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] -apps/job/models/job_event.py:0: error: Argument 1 to "_format_status" has incompatible type "Any | None"; expected "str" [arg-type] -apps/job/models/job_event.py:0: error: Unsupported left operand type for < ("None") [operator] -apps/job/models/job_event.py:0: note: Both left and right operands are unions apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] -apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] apps/job/models/job_event.py:0: error: Argument 1 to "_format_ordinal" has incompatible type "Any | None"; expected "int" [arg-type] +apps/job/models/job_event.py:0: error: Argument 1 to "_format_status" has incompatible type "Any | None"; expected "str" [arg-type] +apps/job/models/job_event.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/job/models/job_event.py:0: error: Call to untyped function (unknown) in typed context [no-untyped-call] +apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/models/job_event.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: "Callable[[dict[Any, Any]], str]" has no attribute "__func__" [attr-defined] -apps/job/models/job_event.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/services/error_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | None" has no attribute "f_back" [union-attr] -apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_back" [union-attr] -apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_code" [union-attr] -apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_code" [union-attr] -apps/workflow/services/error_persistence.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/error_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/services/error_persistence.py:0: error: Incompatible type for "reference_id" of "XeroError" (got "str | None", expected "str | int | Combinable") [misc] -apps/workflow/services/error_persistence.py:0: error: Call to untyped function "_extract_caller_context" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] -apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "DateTimeField[str | datetime | date | Combinable | None, datetime | None]", variable has type "datetime | None") [assignment] -apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] -apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] -apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] -apps/job/models/job.py:0: error: Incompatible types in assignment (expression has type "BooleanField[bool | Combinable, bool]", variable has type "bool") [assignment] -apps/job/models/job.py:0: error: Item "None" of "str | None" has no attribute "rstrip" [union-attr] -apps/job/models/job.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/models/job.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] -apps/job/models/job.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] -apps/job/models/job.py:0: error: Call to untyped function "_detect_field_changes" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_apply_change_side_effects" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_record_change_event" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_clear_assigned_staff_on_archive" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Item "ForeignObjectRel" of "Field[Any, Any] | ForeignObjectRel" has no attribute "verbose_name" [union-attr] -apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_json_safe" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: Call to untyped function "_infer_event_type" of "Job" in typed context [no-untyped-call] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/models/job.py:0: error: "Callable[[Any, Any, Any], Any]" has no attribute "__func__" [attr-defined] -apps/job/tasks.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/tasks.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] -apps/job/tasks.py:0: error: Call to untyped function "create_thumbnail" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Module "bs4" does not explicitly export attribute "NavigableString" [attr-defined] -apps/job/services/workshop_pdf_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/workshop_pdf_service.py:0: error: Need type annotation for "staff_hours" [var-annotated] -apps/job/services/workshop_pdf_service.py:0: error: Incompatible types in assignment (expression has type "list[Table | Flowable]", variable has type "list[Table]") [assignment] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "wait_until_file_ready" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_letterhead_banner" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_handover_section" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_letterhead_banner" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_handover_section" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "Table" has incompatible type "list[object]"; expected "Sequence[list[Any] | tuple[Any, ...]]" [arg-type] -apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/purchasing/models.py:0: error: Call to untyped function "generate_po_number" in typed context [no-untyped-call] -apps/purchasing/models.py:0: error: "PurchaseOrderLine" has no attribute "received_lines" [attr-defined] -apps/purchasing/models.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/models.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -docketworks/celery.py:0: error: Module "celery.signals" has no attribute "task_postrun" [attr-defined] -docketworks/celery.py:0: error: Module "celery.signals" has no attribute "task_prerun" [attr-defined] -docketworks/celery.py:0: error: Untyped decorator makes function "_nplusone_setup" untyped [untyped-decorator] -docketworks/celery.py:0: error: Untyped decorator makes function "_nplusone_teardown" untyped [untyped-decorator] -apps/workflow/services/request.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/workflow/services/request.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/services/time_entry_rates.py:0: error: Item "None" of "Any | None" has no attribute "xero_id" [union-attr] -apps/job/services/time_entry_rates.py:0: error: Item "None" of "Any | None" has no attribute "name" [union-attr] -apps/job/services/time_entry_rates.py:0: error: Incompatible return value type (got "Any | None", expected "XeroPayItem") [return-value] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/company/tests/test_get_company_for_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_get_company_for_xero.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_get_company_for_xero.py:0: error: Call to untyped function "_make_company" in typed context [no-untyped-call] -apps/workflow/management/commands/sync_sequences.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/session_replay_service.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] -apps/workflow/services/session_replay_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/search_telemetry.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/search_telemetry.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] -apps/workflow/api/xero/client.py:0: error: Class cannot subclass "RESTClientObject" (has type "Any") [misc] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_log_quota" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_handle_rate_limit" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_record_quota" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_maybe_log_threshold_warning" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_maybe_log_threshold_warning" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Call to untyped function "_log_summary" in typed context [no-untyped-call] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/timesheet/services/xero_hours.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/timesheet/services/xero_hours.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/timesheet/services/xero_hours.py:0: error: Incompatible return value type (got "dict[str | None, Staff]", expected "dict[str, Staff]") [return-value] -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value -apps/quoting/services/pdf_data_validation.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_logo" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_header_info" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_supplier_info" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_line_items_table" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: List item 0 has incompatible type "Paragraph"; expected "str" [list-item] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: List item 1 has incompatible type "Paragraph"; expected "str" [list-item] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "PurchaseOrderPDFGenerator" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "generate" in typed context [no-untyped-call] -apps/purchasing/services/purchase_order_email_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/purchasing/services/allocation_service.py:0: error: Function "builtins.any" is not valid as a type [valid-type] -apps/purchasing/services/allocation_service.py:0: note: Perhaps you meant "typing.Any" instead of "any"? -apps/purchasing/services/allocation_service.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] -apps/purchasing/services/allocation_service.py:0: error: Incompatible return value type (got "tuple[PurchaseOrderLine | None, Stock]", expected "tuple[PurchaseOrderLine, Stock | CostLine]") [return-value] -apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] -apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/process/tests/conftest.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/conftest.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/diff.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/diff.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/job/diff.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/diff.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/_pdf_golden_fixtures.py:0: error: Incompatible types in assignment (expression has type "Company", variable has type "CompanyDefaults") [assignment] -apps/job/tests/_pdf_golden_fixtures.py:0: error: Returning Any from function declared to return "Job" [no-any-return] -apps/job/services/job_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/job_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/job_service.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] -apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/job_service.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/job_service.py:0: error: Call to untyped function "_touch_job_for_assignment_change" of "JobStaffService" in typed context [no-untyped-call] -apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/job_service.py:0: error: Call to untyped function "_touch_job_for_assignment_change" of "JobStaffService" in typed context [no-untyped-call] -apps/job/services/delivery_docket_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_labour_column" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_material_columns" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "validate_totals" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "find_validation_cells" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/importers/quote_spreadsheet.py:0: error: Unsupported target for indexed assignment ("object") [index] -apps/job/importers/quote_spreadsheet.py:0: error: Unsupported target for indexed assignment ("object") [index] -apps/job/importers/quote_spreadsheet.py:0: error: "object" has no attribute "update" [attr-defined] -apps/job/importers/quote_spreadsheet.py:0: error: Incompatible types in assignment (expression has type "str", variable has type "ValidationError") [assignment] -apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "errors" (hint: "errors: list[] = ...") [var-annotated] -apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "warnings" (hint: "warnings: list[] = ...") [var-annotated] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_labour_column" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "detect_material_columns" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Need type annotation for "conflicts" (hint: "conflicts: list[] = ...") [var-annotated] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "_d" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Call to untyped function "find_validation_cells" in typed context [no-untyped-call] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/quote_spreadsheet.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/importers/google_sheets.py:0: error: Call to untyped function "from_service_account_file" of "Credentials" in typed context [no-untyped-call] -apps/crm/services/phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] -apps/crm/services/phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/services/sales_pipeline_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounting/services/sales_pipeline_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] -apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] -apps/accounting/services/sales_pipeline_service.py:0: error: Incompatible types in assignment (expression has type "float | None", target has type "float") [assignment] -apps/accounting/services/sales_pipeline_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/accounting/services/invoice_calculation.py:0: error: Returning Any from function declared to return "Job" [no-any-return] -apps/company/management/commands/merge_companies.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/management/commands/merge_companies.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/services/providers/gemini_provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "int", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "Any | None", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "int", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "list[dict[str, object]]", target has type "Sequence[str]") [assignment] -apps/quoting/services/providers/gemini_provider.py:0: error: Need type annotation for "supplier_info" (hint: "supplier_info: dict[, ] = ...") [var-annotated] -apps/quoting/services/providers/gemini_provider.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/quoting/services/providers/gemini_provider.py:0: error: Argument "config" to "generate_content" of "Models" has incompatible type "dict[str, object]"; expected "GenerateContentConfig | GenerateContentConfigDict | None" [arg-type] -apps/quoting/services/providers/gemini_provider.py:0: error: Call to untyped function "log_token_usage" in typed context [no-untyped-call] -apps/purchasing/services/quote_to_po_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/purchasing/services/quote_to_po_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/services/quote_to_po_service.py:0: error: Dict entry 0 has incompatible type "str": "dict[str, str]"; expected "str": "str" [dict-item] -apps/purchasing/services/quote_to_po_service.py:0: error: Dict entry 0 has incompatible type "str": "dict[str, str]"; expected "str": "str" [dict-item] -apps/purchasing/services/quote_to_po_service.py:0: error: Argument "contents" to "generate_content" of "Models" has incompatible type "list[dict[str, str]]"; expected "Content | ContentDict | str | Image | File | FileDict | Part | PartDict | list[str | Image | File | FileDict | Part | PartDict] | list[Content | ContentDict | str | Image | File | FileDict | Part | PartDict | list[str | Image | File | FileDict | Part | PartDict]]" [arg-type] -apps/purchasing/services/quote_to_po_service.py:0: error: Call to untyped function "log_token_usage" in typed context [no-untyped-call] -apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "clean_json_response" has incompatible type "str | None"; expected "str" [arg-type] -apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/services/llm_service.py:0: error: Module "litellm" does not explicitly export attribute "set_verbose" [attr-defined] -apps/workflow/services/llm_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Call to untyped function "get_default" of "AIProvider" in typed context [no-untyped-call] -apps/workflow/services/llm_service.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload] -apps/workflow/services/llm_service.py:0: note: Possible overload variants: -apps/workflow/services/llm_service.py:0: note: def get(self, AIProviderTypes, None = ..., /) -> str | None -apps/workflow/services/llm_service.py:0: note: def get(self, AIProviderTypes, str, /) -> str -apps/workflow/services/llm_service.py:0: note: def [_T] get(self, AIProviderTypes, _T, /) -> str | _T -apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] -apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/services/dev_demo_export_scrubber.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_pay_item.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_pay_item.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_pay_item.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_pay_item.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_model.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_model.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/management/commands/create_service_api_key.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/create_service_api_key.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reassign_time_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reassign_time_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reassign_time_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/timesheet/management/commands/create_special_job.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/create_special_job.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/create_leave_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/create_leave_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/management/commands/run_scrapers.py:0: error: Call to untyped function "get_available_scrapers" in typed context [no-untyped-call] -apps/quoting/management/commands/run_scrapers.py:0: error: Argument 5 to "run_scraper" of "Command" has incompatible type "Any | None"; expected "bool" [arg-type] -apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/management/commands/set_paid_flag_jobs.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/management/commands/set_paid_flag_jobs.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_model.py:0: error: Item "None" of "Procedure | None" has no attribute "title" [union-attr] -apps/process/tests/test_procedure_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_model.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_model.py:0: note: Use "-> None" if function does not return a value -apps/process/services/form_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/form_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Call to untyped function "get_default_cost_set_summary" in typed context [no-untyped-call] -apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/workshop_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/utils.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] -apps/accounts/utils.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/utils.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/utils.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/search_telemetry_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/search_telemetry_serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "Callable[..., Any] | str | None") [assignment] -apps/workflow/search_telemetry_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/exception_handlers.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers_scheduled_tasks.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/quoting/serializers_scheduled_tasks.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/quote_spreadsheet_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/company/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/views/build_id_view.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/workflow/api/reports/pnl.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/views/supplier_search_alias_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/month_end_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/month_end_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/job/services/month_end_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "_do_preview" in typed context [no-untyped-call] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "Decimal | CostLine | str | Any", variable has type "CostLine") [assignment] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "Decimal | CostLine | str | Any", variable has type "Decimal") [assignment] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Argument 4 to "_split_costline" of "Command" has incompatible type "Decimal | CostLine | str | Any"; expected "str" [arg-type] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Call to untyped function "_do_preview" in typed context [no-untyped-call] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "date | Decimal | CostSet | Staff | str | Any", variable has type "Staff") [assignment] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "date" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "cost_set" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "CostSet | Combinable") [misc] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "quantity" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | float | Decimal | Combinable") [misc] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "unit_cost" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | float | Decimal | Combinable") [misc] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "accounting_date" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | date | Combinable") [misc] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "str" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "date" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Invalid index type "str | Any | date | Decimal | CostSet | Staff" for "dict[str, int]"; expected type "str" [index] -apps/timesheet/management/commands/create_overtime_entries.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/quoting/mcp.py:0: error: Class cannot subclass "ModelQueryToolset" (has type "Any") [misc] -apps/quoting/mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/mcp.py:0: error: Class cannot subclass "MCPToolset" (has type "Any") [misc] -apps/quoting/mcp.py:0: error: Incompatible default for parameter "supplier_name" (default has type "None", parameter has type "str") [assignment] -apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/quoting/mcp.py:0: error: Incompatible default for parameter "dimensions" (default has type "None", parameter has type "str") [assignment] -apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/quoting/mcp.py:0: error: Incompatible default for parameter "labor_hours" (default has type "None", parameter has type "float") [assignment] -apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/quoting/mcp.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/quoting/mcp.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] -apps/quoting/mcp.py:0: error: Incompatible default for parameter "supplier_name" (default has type "None", parameter has type "str") [assignment] -apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/quoting/mcp.py:0: error: Need type annotation for "supplier_prices" (hint: "supplier_prices: dict[, ] = ...") [var-annotated] -apps/operations/services/scheduler_service.py:0: error: Missing type arguments for generic type "frozenset" [type-arg] -apps/operations/services/scheduler_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/operations/services/scheduler_service.py:0: error: Missing type arguments for generic type "frozenset" [type-arg] -apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] -apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] -apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] -apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] -apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] -apps/operations/services/scheduler_service.py:0: error: Missing type arguments for generic type "frozenset" [type-arg] -apps/job/services/job_profitability_report.py:0: error: Call to untyped function "_get_queryset" in typed context [no-untyped-call] -apps/job/services/job_profitability_report.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/job_profitability_report.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/job_profitability_report.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/job_profitability_report.py:0: error: Item "float" of "Decimal | float" has no attribute "quantize" [union-attr] -apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] -apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] -apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | int" has no attribute "quantize" [union-attr] -apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] -apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | int" has no attribute "quantize" [union-attr] -apps/job/services/import_quote_service.py:0: error: Unsupported operand types for + ("None" and "int") [operator] -apps/job/services/import_quote_service.py:0: note: Left operand is of type "Any | None" -apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, Any | int | None]", target has type "list[dict[str, Any]] | bool | None") [assignment] -apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, int | None]", target has type "list[dict[str, Any]] | bool | None") [assignment] -apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, int]", target has type "list[dict[str, Any]] | bool | None") [assignment] -apps/job/services/import_quote_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/import_quote_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/import_quote_service.py:0: error: Unsupported operand types for + ("None" and "int") [operator] -apps/job/services/import_quote_service.py:0: note: Left operand is of type "Any | None" -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/services/google_docs_service.py:0: note: Use "-> None" if function does not return a value -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/process/services/google_docs_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "tuple" [type-arg] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Argument 3 to "_upload_to_google_docs" of "Command" has incompatible type "str | None"; expected "str" [arg-type] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Incompatible types in assignment (expression has type "Procedure", variable has type "Form") [assignment] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Need type annotation for "candidates" (hint: "candidates: dict[, ] = ...") [var-annotated] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Call to untyped function "from_service_account_file" of "Credentials" in typed context [no-untyped-call] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "tuple" [type-arg] -apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Call to untyped function "_get_drive_service" in typed context [no-untyped-call] -apps/workflow/services/db_scrubber.py:0: error: Argument 1 to "add" of "set" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/services/data_integrity_service.py:0: error: Dict entry 4 has incompatible type "str": "list[str]"; expected "str": "str" [dict-item] -apps/job/services/data_integrity_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/accounting/services/wip_service.py:0: error: Argument "key" to "sorted" has incompatible type "Callable[[dict[str, object]], object]"; expected "Callable[[dict[str, object]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] -apps/accounting/services/wip_service.py:0: error: Incompatible return value type (got "object", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] -apps/workflow/management/commands/e2e_cleanup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/e2e_cleanup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/services/ai_price_extraction.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/services/ai_price_extraction.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "int" [call-overload] -apps/quoting/services/ai_price_extraction.py:0: note: Possible overload variants: -apps/quoting/services/ai_price_extraction.py:0: note: def get(self, AIProviderTypes, None = ..., /) -> int | None -apps/quoting/services/ai_price_extraction.py:0: note: def get(self, AIProviderTypes, int, /) -> int -apps/quoting/services/ai_price_extraction.py:0: note: def [_T] get(self, AIProviderTypes, _T, /) -> int | _T -apps/quoting/services/ai_price_extraction.py:0: error: Call to untyped function "get_prioritized_active_providers" in typed context [no-untyped-call] -apps/process/services/safety_ai_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/services/safety_ai_service.py:0: note: Use "-> None" if function does not return a value -apps/process/services/safety_ai_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] -apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "list[str]" [no-any-return] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "list[dict[str, str]]" [no-any-return] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] -apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] -apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_dev_demo_export.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_dev_demo_export.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_form_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_form_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_form_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_form_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/timesheet/services/weekly_timesheet_service.py:0: error: Need type annotation for "lines_by_staff_day" (hint: "lines_by_staff_day: dict[, ] = ...") [var-annotated] -apps/timesheet/services/weekly_timesheet_service.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/services/daily_timesheet_service.py:0: error: Call to untyped function "ensure_json_serializable" in typed context [no-untyped-call] -apps/timesheet/services/daily_timesheet_service.py:0: error: Call to untyped function "ensure_json_serializable" in typed context [no-untyped-call] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_determine_status" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_get_staff_alerts" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "int"; expected "Decimal" [arg-type] -apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "int"; expected "Decimal" [arg-type] -apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "int | str | Any" -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] -apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] -apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("int" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "int") [operator] -apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("int" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "int") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("int" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "int") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "CostLine", variable has type "CostLine@AnnotatedWith[TypedDict({'is_billable': str})]") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "CostLine", variable has type "CostLine@AnnotatedWith[TypedDict({'is_billable': str})]") [assignment] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: note: Right operand is of type "Decimal | int" -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("float" and "Decimal") [operator] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "Decimal") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Argument 2 to "_get_color" of "KPIService" has incompatible type "Decimal"; expected "float" [arg-type] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "Decimal", target has type "float") [assignment] -apps/accounting/services/core.py:0: error: Argument "key" to "max" has incompatible type "Callable[[dict[str, object]], object]"; expected "Callable[[dict[str, object]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] -apps/accounting/services/core.py:0: error: Incompatible return value type (got "object", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] -apps/accounting/services/core.py:0: error: Argument 1 to "localtime" has incompatible type "object"; expected "datetime | None" [arg-type] -apps/accounting/services/core.py:0: error: Incompatible types in assignment (expression has type "object", variable has type "date") [assignment] -apps/accounting/services/core.py:0: error: Incompatible return value type (got "None", expected "dict[str, Any]") [return-value] -apps/accounting/services/core.py:0: error: Generator has incompatible item type "Decimal"; expected "bool" [misc] -apps/accounting/services/core.py:0: error: "CostLine" has no attribute "is_billable_meta" [attr-defined] -apps/accounting/services/core.py:0: error: Argument 1 to "_calculate_job_breakdown" of "StaffPerformanceService" has incompatible type "list[CostLine]"; expected "QuerySet[Any, Any]" [arg-type] -apps/accounting/services/core.py:0: error: Missing type arguments for generic type "QuerySet" [type-arg] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("float" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for + ("str" and "float") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("float" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for - ("str" and "float") [operator] -apps/accounting/services/core.py:0: error: Unsupported left operand type for - ("str") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for / ("float" and "str") [operator] -apps/accounting/services/core.py:0: error: Unsupported operand types for / ("str" and "float") [operator] -apps/accounting/services/core.py:0: error: Unsupported left operand type for / ("str") [operator] -apps/accounting/services/core.py:0: note: Both left and right operands are unions -apps/accounting/services/core.py:0: error: Unsupported operand types for > ("str" and "int") [operator] -apps/accounting/services/core.py:0: note: Left operand is of type "float | Any | str" -apps/job/services/kanban_service.py:0: error: Incompatible types in assignment (expression has type "list[Job]", variable has type "JobQuerySet") [assignment] -apps/job/services/kanban_service.py:0: error: Argument 1 to "_rank_kanban_search_candidates" of "KanbanService" has incompatible type "list[Job@AnnotatedWith[TypedDict({'trigram_score': Any})]]"; expected "list[Job]" [arg-type] -apps/job/services/kanban_service.py:0: note: "list" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance -apps/job/services/kanban_service.py:0: note: Consider using "Sequence" instead, which is covariant -apps/job/services/kanban_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/job/services/kanban_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/job/services/kanban_service.py:0: error: Value of type "list[dict[str, int | str | Any | None]] | int | dict[str, Any] | str | Any | None" is not indexable [index] -apps/job/services/kanban_service.py:0: error: Invalid index type "slice[None, int, None]" for "dict[str, Any]"; expected type "str" [index] -apps/job/services/kanban_service.py:0: error: Call to untyped function "filter_kanban_jobs" of "KanbanService" in typed context [no-untyped-call] -apps/job/services/kanban_service.py:0: error: Returning Any from function declared to return "QuerySet[Job, Job]" [no-any-return] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/kanban_service.py:0: error: Returning Any from function declared to return "float" [no-any-return] -apps/job/services/kanban_service.py:0: error: Returning Any from function declared to return "float" [no-any-return] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/kanban_service.py:0: error: Call to untyped function "filter_kanban_jobs" of "KanbanService" in typed context [no-untyped-call] -apps/job/services/kanban_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/testing.py:0: note: Use "-> None" if function does not return a value -apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/testing.py:0: note: Use "-> None" if function does not return a value -apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] -apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] -apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/testing.py:0: note: Use "-> None" if function does not return a value -apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] -apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] -apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/testing.py:0: note: Use "-> None" if function does not return a value -apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] -apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] -apps/workflow/serializers.py:0: error: Returning Any from function declared to return "str | None" [no-any-return] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "JSONField", base class "Field" defined the type as "dict[str, Any]") [assignment] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Field" defined the type as "dict[str, str | _StrPromise]") [assignment] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "JSONField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "BooleanField", base class "Field" defined the type as "bool") [assignment] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "BooleanField", base class "Field" defined the type as "bool") [assignment] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "SettingsFieldSerializer", base class "Serializer" defined the type as "BindingDict") [assignment] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_office_staff" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_office_staff" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_office_staff" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_office_staff" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_office_staff" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Call to untyped function "_row" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_app_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_app_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] -apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_search_telemetry.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_search_telemetry.py:0: error: Call to untyped function "result_ids" in typed context [no-untyped-call] -apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_job" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_job" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_job" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "assign_staff_to_job" of "JobStaffService" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_job" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_client" in typed context [no-untyped-call] -apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "_job" in typed context [no-untyped-call] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_purchase_order_line_usage.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_purchase_order_line_usage.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "JobQuoteChatSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/accounting/serializers/rdti_spend_serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] -apps/company/views/supplier_pickup_address_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/views/supplier_pickup_address_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/views/contact_method_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -apps/company/views/contact_method_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/views/contact_method_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/data_versions_view.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Need type annotation for "jobs_through_quotes" (hint: "jobs_through_quotes: set[] = ...") [var-annotated] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_event_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_event_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Returning Any from function declared to return "float" [no-any-return] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Incompatible types in assignment (expression has type "SerializerMethodField", base class "CostSetSerializer" defined the type as "CostLineSerializer") [assignment] -apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "start_date" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Argument 1 to "_get_total_invoiced_for_job" of "SalesForecastMonthDetailAPIView" has incompatible type "Job | None"; expected "Job" [arg-type] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "latest_actual" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "company" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Company | Any | None" has no attribute "name" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "job_number" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/accounting/views/sales_forecast_view.py:0: error: Argument "row_date" to "build_row" has incompatible type "Any | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_base_manager.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/views/xero/xero_base_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_base_manager.py:0: note: Use "-> None" if function does not return a value -apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] -apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] -apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Call to untyped function "_hydrate_company_search_results" of "CompanyRestService" in typed context [no-untyped-call] -apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Value of type "list[dict[str, Any]] | int | Any | str | None" is not indexable [index] -apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] -apps/company/services/company_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/services/company_rest_service.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] -apps/company/views/company_rest_views.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/company/views/company_rest_views.py:0: error: Argument 1 to "get_company_by_id" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] -apps/company/views/company_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/views/company_rest_views.py:0: error: Argument 1 to "update_company" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] -apps/company/views/company_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/views/company_rest_views.py:0: error: Incompatible types in assignment (expression has type "CompanyDuplicateErrorResponseSerializer", variable has type "CompanyErrorResponseSerializer") [assignment] -apps/company/views/company_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/views/company_rest_views.py:0: error: Argument 1 to "get_company_jobs" of "CompanyRestService" has incompatible type "str"; expected "UUID" [arg-type] -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: error: Call to untyped function "date_to_datetime" in typed context [no-untyped-call] -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: note: Use "-> None" if function does not return a value -apps/company/tests/test_company_invoice_summary.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_company_invoice_summary.py:0: note: Use "-> None" if function does not return a value -apps/job/services/quote_mode_controller.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/quote_mode_controller.py:0: note: Use "-> None" if function does not return a value -apps/job/services/quote_mode_controller.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/quote_mode_controller.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] -apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/services/quote_mode_controller.py:0: error: Cannot call function of unknown type [operator] -apps/job/services/quote_mode_controller.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/services/quote_mode_controller.py:0: error: Incompatible return value type (got "None", expected "str") [return-value] -apps/job/services/mcp_chat_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/job/services/mcp_chat_service.py:0: error: Dict entry 1 has incompatible type "str": "str | None"; expected "str": "str" [dict-item] -apps/job/services/mcp_chat_service.py:0: error: Dict entry 2 has incompatible type "str": "list[dict[str, dict[str, Any | str] | Any | str]]"; expected "str": "str" [dict-item] -apps/job/services/quote_sync_service.py:0: error: Call to untyped function "_copy_estimate_to_quote_costset" in typed context [no-untyped-call] -apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/quote_sync_service.py:0: error: "" has no attribute "LABOR" [attr-defined] -apps/job/services/quote_sync_service.py:0: error: "" has no attribute "MATERIAL" [attr-defined] -apps/job/services/quote_sync_service.py:0: error: Argument "quantity" to "DraftLine" has incompatible type "float | int"; expected "Decimal" [arg-type] -apps/job/services/quote_sync_service.py:0: error: Argument "unit_cost" to "DraftLine" has incompatible type "float | int"; expected "Decimal" [arg-type] -apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/services/quote_sync_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/quote_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: error: Item "None" of "type[InvoiceLineItem] | type[BillLineItem] | type[CreditNoteLineItem] | None" has no attribute "objects" [union-attr] -apps/workflow/api/xero/reprocess_xero.py:0: error: Argument after ** must have string keys [arg-type] -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_companies" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_invoices" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_bills" in typed context [no-untyped-call] -apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_credit_notes" in typed context [no-untyped-call] -apps/job/views/data_integrity_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/services/pdf_import_service.py:0: note: Use "-> None" if function does not return a value -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: "object" has no attribute "append" [attr-defined] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Incompatible return value type (got "None", expected "SupplierProduct") [return-value] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] -apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/services/pdf_import_service.py:0: note: Use "-> None" if function does not return a value -apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: error: Incompatible types in assignment (expression has type "WebDriver", variable has type "None") [assignment] -apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value -apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value -apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value -apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value -apps/quoting/scrapers/base.py:0: error: Call to untyped function "setup_driver" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "login" of "BaseScraper" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "get_product_urls" of "BaseScraper" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "scrape_product" of "BaseScraper" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "save_products" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "save_products" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Call to untyped function "cleanup" in typed context [no-untyped-call] -apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/services/procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/services/procedure_service.py:0: note: Use "-> None" if function does not return a value -apps/process/services/procedure_service.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] -apps/process/services/procedure_service.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Key expression in dictionary comprehension has incompatible type "str | None"; expected type "str" [misc] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] -apps/accounting/services/payroll_reconciliation_service.py:0: error: Invalid index type "str | None" for "dict[str, dict[str, Any]]"; expected type "str" [index] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Value of type "tuple[str, str, int] | None" is not indexable [index] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Value of type "tuple[str, str, int] | None" is not indexable [index] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Need type annotation for "all_field_keys" (hint: "all_field_keys: list[] = ...") [var-annotated] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_schema.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_schema.py:0: error: "Client" has no attribute "force_authenticate" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_schema.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_company_defaults_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_api.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_api.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_company_defaults_api.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_company_defaults_api.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_api.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/workflow/tests/test_company_defaults_api.py:0: error: "_MonkeyPatchedWSGIResponse" has no attribute "data" [attr-defined] -apps/timesheet/tests/test_weekly_timesheet_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_weekly_timesheet_service.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_weekly_timesheet_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_weekly_timesheet_service.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_permissions.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_permissions.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_payroll_post_api.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_pay_run_list_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_pay_run_list_api.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_pay_run_list_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_pay_run_list_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_pay_run_list_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/tests/test_jobs_api_filter.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/tests/test_jobs_api_filter.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/timesheet/tests/test_jobs_api_filter.py:0: error: Returning Any from function declared to return "Job" [no-any-return] -apps/timesheet/tests/test_daily_timesheet_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_daily_timesheet_service.py:0: note: Use "-> None" if function does not return a value -apps/timesheet/tests/test_daily_timesheet_service.py:0: error: Returning Any from function declared to return "Job" [no-any-return] -apps/timesheet/tests/test_daily_timesheet_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/tests/test_daily_timesheet_service.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests_mcp.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/quoting/tests/test_pdf_data_validation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/tests/test_pdf_data_validation.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_workshop_schedule_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_workshop_schedule_api.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_make_special_leave_job" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_book_time" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_make_special_leave_job" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_book_time" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertGreater" of "TestCase" [misc] -apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertGreater" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderGT[Any]" [arg-type] -apps/operations/tests/test_scheduler_service.py:0: error: Incompatible type for lookup 'allocation_date': (got "date | None", expected "str | date") [misc] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_set_efficiency" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_set_efficiency" in typed context [no-untyped-call] -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertGreater" of "TestCase" [misc] -apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertGreater" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderGT[Any]" [arg-type] -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_service.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertLessEqual" of "TestCase" [misc] -apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertLessEqual" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderLE[Any]" [arg-type] -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/operations/tests/test_scheduler_persistence.py:0: note: Use "-> None" if function does not return a value -apps/operations/tests/test_scheduler_persistence.py:0: error: Item "None" of "SchedulerRun | None" has no attribute "id" [union-attr] -apps/job/tests/test_workshop_timesheet_api.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/tests/test_workshop_timesheet_api.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_workshop_pdf_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_workshop_pdf_service.py:0: error: Call to untyped function "convert_html_to_reportlab" in typed context [no-untyped-call] -apps/job/tests/test_pdf_goldens.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_pdf_goldens.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_pdf_goldens.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_pdf_goldens.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_pdf_goldens.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_paid_flag_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_paid_flag_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_paid_flag_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_paid_flag_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Missing positional argument "query" in call to "search_products" of "QuotingTool" [call-arg] -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_mcp_tool_integration.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_mcp_tool_integration.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_quote_chat_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_quote_chat_model.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_quote_chat_model.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_quote_chat_model.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_latest_costsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_latest_costsets.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_invoicing.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/job/tests/test_job_invoicing.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/job/tests/test_job_files_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_files_api.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_files_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_files_api.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_files_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_files_api.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_files_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] -apps/job/tests/test_job_files_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_files_api.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delivery_docket_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delivery_docket_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delivery_docket_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delivery_docket_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_delivery_docket_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_delivery_docket_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_costline_schema_validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/tests/test_costline_schema_validation.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/job/tests/test_costline_schema_validation.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Need type annotation for "data" (hint: "data: dict[, ] = ...") [var-annotated] -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_api_endpoints.py:0: note: Use "-> None" if function does not return a value -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/crm/tests/test_phone_call_service.py:0: error: Cannot assign to a method [method-assign] -apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] -apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] -apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] -apps/crm/tests/test_phone_call_service.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "timedelta" [attr-defined] -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/crm/tests/test_phone_call_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/tests/test_company_merge_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/company/tests/test_company_merge_service.py:0: error: Value of type "Any | None" is not indexable [index] -apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_merge_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/tests/test_staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_staff_api.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_displayable_staff.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_displayable_staff.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_automation_user.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_automation_user.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_automation_user.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_automation_user.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Call to untyped function "setUpTestData" of "BaseAPITestCase" in typed context [no-untyped-call] -apps/accounting/tests/test_sales_pipeline_api.py:0: error: "type[SalesPipelineAPITests]" has no attribute "office_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_api.py:0: error: "SalesPipelineAPITests" has no attribute "office_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_api.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_models.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_models.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_invoice" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_invoice_calculation.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_invoice_calculation.py:0: error: Call to untyped function "_add_revenue_line" in typed context [no-untyped-call] -apps/accounting/tests/test_core_nplusone.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_core_nplusone.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_core_nplusone.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] -apps/accounting/tests/test_core_nplusone.py:0: error: Returning Any from function declared to return "Job" [no-any-return] -apps/accounting/tests/test_core_nplusone.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_core_nplusone.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_core_nplusone.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_core_nplusone.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_core_nplusone.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_create_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_service.py:0: error: Call to untyped function "_set_summary_revenue" in typed context [no-untyped-call] -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] -apps/job/tests/test_kanban_search.py:0: error: Incompatible type for "company" of "Invoice" (got "Company | None", expected "Company | Combinable") [misc] -apps/job/tests/test_kanban_search.py:0: error: Incompatible type for "company" of "Quote" (got "Company | None", expected "Company | Combinable") [misc] -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_search.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_ordered_names" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_ordered_names" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_ordered_names" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_ordered_names" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_kanban_reorder_priority.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_kanban_reorder_priority.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/session_replay_view.py:0: error: Call to untyped function "has_permission" in typed context [no-untyped-call] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_logo_api.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_logo_api.py:0: error: Call to untyped function "_remove_if_user_uploaded" in typed context [no-untyped-call] -apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_logo_api.py:0: error: Call to untyped function "_remove_if_user_uploaded" in typed context [no-untyped-call] -apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/operations/views/workshop_schedule_view.py:0: error: Incompatible types in assignment (expression has type "list[JobProjection]", variable has type "QuerySet[JobProjection, JobProjection]") [assignment] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/views/job_profitability_report_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_files_collection_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/views/job_files_collection_view.py:0: error: "Callable[[str], None]" has no attribute "delay" [attr-defined] -apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_files_collection_view.py:0: error: Call to untyped function "save_file" in typed context [no-untyped-call] -apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_thumbnail_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] -apps/job/views/data_quality_report_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/authentication.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/authentication.py:0: error: Returning Any from function declared to return "bool" [no-any-return] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Call to untyped function "get_raw_token_from_cookie" in typed context [no-untyped-call] -apps/workflow/authentication.py:0: error: "AbstractBaseUser" has no attribute "is_currently_active" [attr-defined] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Call to untyped function "mark_used" in typed context [no-untyped-call] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/authentication.py:0: error: Call to untyped function "mark_used" in typed context [no-untyped-call] -apps/workflow/extensions.py:0: error: Call to untyped function "__init_subclass__" in typed context [no-untyped-call] -apps/workflow/extensions.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/accounts/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/accounts/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/accounts/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/mixins.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg] -apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/mixins.py:0: error: Call to untyped function "get_job_by_number" in typed context [no-untyped-call] -apps/job/mixins.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/mixins.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/workflow/views/xero_pay_item_viewset.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] -apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] -apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "RetrieveAPIView" [type-arg] -apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] -apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/ai_provider_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/views_scheduled_tasks.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] -apps/quoting/views_scheduled_tasks.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] -apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Call to untyped function "_apply_category_filter" in typed context [no-untyped-call] -apps/process/views/form_viewsets.py:0: error: Call to untyped function "_apply_common_filters" in typed context [no-untyped-call] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Argument "document_type" to "create_form" of "FormService" has incompatible type "Sequence[str]"; expected "str" [arg-type] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Call to untyped function "update" of "FormViewSet" in typed context [no-untyped-call] -apps/process/views/form_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] -apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Call to untyped function "get_form" in typed context [no-untyped-call] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_api_schema_coverage.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_api_schema_coverage.py:0: error: Call to untyped function "_get_all_url_patterns" in typed context [no-untyped-call] -apps/workflow/tests/test_api_schema_coverage.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_api_schema_coverage.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_api_schema_coverage.py:0: error: Call to untyped function "_get_all_url_patterns" in typed context [no-untyped-call] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Argument "default" to "DecimalField" has incompatible type "int"; expected "Decimal | Callable[[], Decimal] | _Empty | None" [arg-type] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Argument "default" to "DecimalField" has incompatible type "int"; expected "Decimal | Callable[[], Decimal] | _Empty | None" [arg-type] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "Callable[..., Any] | str | None") [assignment] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[Any, Any]]") [return-value] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[Any, Any]]") [return-value] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "JobSummaryDataSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "JobDataSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "ModelSerializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Returning Any from function declared to return "bool | None" [no-any-return] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Returning Any from function declared to return "str | None" [no-any-return] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Argument "choices" to "ChoiceField" has incompatible type "Sequence[tuple[Any, Any]] | Sequence[tuple[str, Iterable[tuple[Any, Any]]]] | None"; expected "Sequence[Any]" [arg-type] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "BindingDict") [assignment] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quoted" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_quote_manager.py:0: note: Use "-> None" if function does not return a value -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quoted" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Argument "client_external_id" to "QuotePayload" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "state_valid_for_xero" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Incompatible type for "xero_id" of "Quote" (got "str | None", expected "str | UUID") [misc] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Argument 2 to "_add_xero_history_note" of "XeroDocumentManager" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "get_xero_id" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] -apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "str" [arg-type] -apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "Decimal" [arg-type] -apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "str | None" [arg-type] -apps/workflow/views/xero/xero_po_manager.py:0: error: Argument "supplier_external_id" to "POPayload" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_po_manager.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "validate_for_xero_sync" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_po_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_po_manager.py:0: note: Use "-> None" if function does not return a value -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Cannot resolve keyword 'created_at' into field. Choices are: amount_due, billing_metadata, company, company_id, date, django_created_at, django_updated_at, due_date, id, job, job_id, line_items, number, online_url, raw_json, status, tax, total_excl_tax, total_incl_tax, xero_id, xero_last_modified, xero_last_synced, xero_tenant_id [misc] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "paid" [union-attr] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument "client_external_id" to "InvoicePayload" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "state_valid_for_xero" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument 2 to "_add_xero_history_note" of "XeroDocumentManager" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument 1 to "_attach_workshop_pdf" of "XeroInvoiceManager" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "get_xero_id" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/company/tests/test_company_fts_search.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "Staff | AnonymousUser") [assignment] -apps/company/tests/test_company_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/services/chat_service.py:0: error: Call to untyped function "QuoteModeController" in typed context [no-untyped-call] -apps/job/services/chat_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] -apps/job/services/chat_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/job/services/chat_service.py:0: error: Cannot call function of unknown type [operator] -apps/quoting/views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/views.py:0: error: Call to untyped function "PDFImportService" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement | bool]"; expected "Callable[[None], object]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "is_product_url" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "login" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "page_source" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_text_by_selector" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_text_by_selector" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_description" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_specifications" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_price_unit" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_all_variants" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_elements" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_variants_direct" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_variants_for_width" in typed context [no-untyped-call] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] -apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] -apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_apply_category_filter" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_apply_common_filters" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Argument "document_type" to "create_blank_procedure" of "ProcedureService" has incompatible type "Sequence[str]"; expected "str" [arg-type] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "update" of "ProcedureViewSet" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_get_procedure" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_get_procedure" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "dict[str, Any]") [assignment] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] -apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] -apps/process/urls_rest.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/process/tests/test_procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_service.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] -apps/process/tests/test_procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/process/tests/test_procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/process/tests/test_procedure_service.py:0: note: Use "-> None" if function does not return a value -apps/process/tests/test_procedure_service.py:0: error: Call to untyped function "_make_service" in typed context [no-untyped-call] -apps/workflow/api/reports/payroll_reconciliation.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/job/views/workshop_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/job/views/workshop_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] -apps/job/views/workshop_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/workshop_view.py:0: error: Incompatible type for lookup 'people__id': (got "UUID | None", expected "UUID | str") [misc] -apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/middleware.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] -apps/accounts/views/user_profile_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/accounts/views/token_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/views/token_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] -apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] -apps/accounts/views/token_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/views/token_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/accounts/views/token_view.py:0: error: Item "None" of "Any | None" has no attribute "total_seconds" [union-attr] -apps/accounts/views/staff_views.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] -apps/accounts/views/staff_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/views/staff_views.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] -apps/accounts/views/staff_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/views/staff_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/views/staff_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/views/staff_api.py:0: error: Missing type arguments for generic type "ListCreateAPIView" [type-arg] -apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/views/staff_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/views/staff_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/views/staff_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/supplier_search_rest_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/purchasing/tests/test_serializers.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_serializers.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_serializers.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_serializers.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_serializers.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] -apps/purchasing/tests/test_serializers.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_serializers.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_serializers.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] -apps/purchasing/services/stock_search_service.py:0: error: Argument 1 to "add" of "set" has incompatible type "tuple[float, ...]"; expected "tuple[float, float]" [arg-type] -apps/purchasing/services/stock_search_service.py:0: error: Argument 1 to "add" of "set" has incompatible type "tuple[float, ...]"; expected "tuple[float, float]" [arg-type] -apps/purchasing/services/stock_search_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/services/stock_search_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/stock_search_service.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[str, Any]]") [return-value] -apps/purchasing/services/stock_search_service.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Stock, Stock]", variable has type "list[Stock]") [assignment] -apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Dict entry 0 has incompatible type "str": "str | _StrPromise"; expected "str": "str" [dict-item] -apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_costline_views.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/job/views/workshop_pdf_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/month_end_rest_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/month_end_rest_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/month_end_rest_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "id" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "meta" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "quantity" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "unit_cost" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "calculated_total_cost" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostSet | None" has no attribute "rev" [union-attr] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/delivery_docket_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/archive_completed_jobs_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] -apps/job/views/archive_completed_jobs_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/archive_completed_jobs_view.py:0: error: Call to untyped function "get_paid_complete_jobs" in typed context [no-untyped-call] -apps/job/views/archive_completed_jobs_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/archive_completed_jobs_view.py:0: error: Call to untyped function "archive_complete_jobs" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Call to untyped function "_FakeProvider" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "_FakeProvider", variable has type "AccountingProvider") [assignment] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Cannot assign to a method [method-assign] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "def _attach_workshop_pdf(self, external_id: str) -> None", variable has type "def _attach_workshop_pdf(self, invoice_external_id: str) -> str | None") [assignment] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_document_raw_json.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Call to untyped function "_FakeProvider" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "_FakeProvider", variable has type "AccountingProvider") [assignment] -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Call to untyped function "QuoteModeController" in typed context [no-untyped-call] -apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_quote_modes.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/job/tests/test_chat_service.py:0: error: Incompatible default for parameter "tool_call_id" (default has type "None", parameter has type "str") [assignment] -apps/job/tests/test_chat_service.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/job/tests/test_chat_service.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value +apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/models/job_event.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/models/job_event.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/models/job_event.py:0: error: Unsupported left operand type for < ("None") [operator] +apps/job/models/job_event.py:0: note: Both left and right operands are unions +apps/job/models/job_file.py:0: error: Argument 1 to "get_job_folder_path" has incompatible type "int"; expected "str" [arg-type] +apps/job/models/job_file.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] +apps/job/models/spreadsheet.py:0: error: Item "None" of "Job | None" has no attribute "job_number" [union-attr] +apps/job/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/costing_serializer.py:0: error: Incompatible types in assignment (expression has type "SerializerMethodField", base class "CostSetSerializer" defined the type as "CostLineSerializer") [assignment] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/job/serializers/costing_serializer.py:0: error: Returning Any from function declared to return "float" [no-any-return] +apps/job/serializers/costing_serializer.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_integrity_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/data_quality_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_file_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_profitability_report_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "DictField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Incompatible types in assignment (expression has type "JobQuoteChatSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_quote_chat_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Argument "choices" to "ChoiceField" has incompatible type "Sequence[tuple[Any, Any]] | Sequence[tuple[str, Iterable[tuple[Any, Any]]]] | None"; expected "Sequence[Any]" [arg-type] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/serializers/job_serializer.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[Any, Any]]") [return-value] +apps/job/serializers/job_serializer.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[Any, Any]]") [return-value] +apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "JobDataSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "JobSummaryDataSerializer", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "BindingDict") [assignment] +apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/serializers/job_serializer.py:0: error: Returning Any from function declared to return "bool | None" [no-any-return] +apps/job/serializers/job_serializer.py:0: error: Returning Any from function declared to return "str | None" [no-any-return] +apps/job/serializers/kanban_serializer.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/kanban_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/serializers/quote_sync_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/job/services/chat_service.py:0: error: Call to untyped function "QuoteModeController" in typed context [no-untyped-call] +apps/job/services/chat_service.py:0: error: Cannot call function of unknown type [operator] +apps/job/services/chat_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/job/services/chat_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/services/data_integrity_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/services/data_integrity_service.py:0: error: Dict entry 4 has incompatible type "str": "list[str]"; expected "str": "str" [dict-item] +apps/job/services/delivery_docket_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/services/delta_checksum.py:0: error: Argument 1 to "join" of "str" has incompatible type "Decimal"; expected "Iterable[str]" [arg-type] +apps/job/services/delta_checksum.py:0: error: Incompatible types in assignment (expression has type "list[str]", variable has type "Decimal") [assignment] +apps/job/services/file_service.py:0: error: Call to untyped function "create_thumbnail" in typed context [no-untyped-call] +apps/job/services/file_service.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] +apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/file_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/file_service.py:0: error: Incompatible types in assignment (expression has type "Image", variable has type "ImageFile") [assignment] +apps/job/services/import_quote_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/import_quote_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, Any | int | None]", target has type "list[dict[str, Any]] | bool | None") [assignment] +apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, int | None]", target has type "list[dict[str, Any]] | bool | None") [assignment] +apps/job/services/import_quote_service.py:0: error: Incompatible types in assignment (expression has type "dict[str, int]", target has type "list[dict[str, Any]] | bool | None") [assignment] +apps/job/services/import_quote_service.py:0: error: Unsupported operand types for + ("None" and "int") [operator] +apps/job/services/import_quote_service.py:0: error: Unsupported operand types for + ("None" and "int") [operator] +apps/job/services/import_quote_service.py:0: note: Left operand is of type "Any | None" +apps/job/services/import_quote_service.py:0: note: Left operand is of type "Any | None" +apps/job/services/job_profitability_report.py:0: error: Call to untyped function "_get_queryset" in typed context [no-untyped-call] +apps/job/services/job_profitability_report.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/job_profitability_report.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/job_profitability_report.py:0: error: Item "float" of "Decimal | float" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | Literal[0]" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | int" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Item "int" of "Decimal | int" has no attribute "quantize" [union-attr] +apps/job/services/job_profitability_report.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/job_rest_service.py:0: error: "Job" has no attribute "pricings" [attr-defined] +apps/job/services/job_rest_service.py:0: error: "ValueError" has no attribute "delta_rejection_context" [attr-defined] +apps/job/services/job_rest_service.py:0: error: Argument "key" to "sort" of "list" has incompatible type "Callable[[dict[str, int | datetime | UUID | str | Any | None]], int | datetime | UUID | str | Any | None]"; expected "Callable[[dict[str, int | datetime | UUID | str | Any | None]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] +apps/job/services/job_rest_service.py:0: error: Assignment to variable "exc" outside except: block [misc] +apps/job/services/job_rest_service.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] +apps/job/services/job_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/job_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/job_rest_service.py:0: error: Incompatible default for parameter "week" (default has type "None", parameter has type "date") [assignment] +apps/job/services/job_rest_service.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[str, Any]]") [return-value] +apps/job/services/job_rest_service.py:0: error: Incompatible return value type (got "int | datetime | UUID | str | Any | None", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] +apps/job/services/job_rest_service.py:0: error: Trying to read deleted variable "exc" [misc] +apps/job/services/job_rest_service.py:0: error: Trying to read deleted variable "exc" [misc] +apps/job/services/job_rest_service.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/job/services/job_rest_service.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/job/services/job_service.py:0: error: Call to untyped function "_touch_job_for_assignment_change" of "JobStaffService" in typed context [no-untyped-call] +apps/job/services/job_service.py:0: error: Call to untyped function "_touch_job_for_assignment_change" of "JobStaffService" in typed context [no-untyped-call] +apps/job/services/job_service.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] +apps/job/services/job_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/job_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/job_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_categorization_service.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/job/services/kanban_categorization_service.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/job/services/kanban_service.py:0: error: Argument 1 to "_rank_kanban_search_candidates" of "KanbanService" has incompatible type "list[Job@AnnotatedWith[TypedDict({'trigram_score': Any})]]"; expected "list[Job]" [arg-type] +apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/kanban_service.py:0: error: Incompatible types in assignment (expression has type "list[Job]", variable has type "JobQuerySet") [assignment] +apps/job/services/kanban_service.py:0: error: Invalid index type "slice[None, int, None]" for "dict[str, Any]"; expected type "str" [index] +apps/job/services/kanban_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/job/services/kanban_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/job/services/kanban_service.py:0: error: Returning Any from function declared to return "float" [no-any-return] +apps/job/services/kanban_service.py:0: error: Returning Any from function declared to return "float" [no-any-return] +apps/job/services/kanban_service.py:0: error: Value of type "list[dict[str, int | str | Any | None]] | int | dict[str, Any] | str | Any | None" is not indexable [index] +apps/job/services/kanban_service.py:0: note: "list" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance +apps/job/services/kanban_service.py:0: note: Consider using "Sequence" instead, which is covariant +apps/job/services/mcp_chat_service.py:0: error: Dict entry 1 has incompatible type "str": "str | None"; expected "str": "str" [dict-item] +apps/job/services/mcp_chat_service.py:0: error: Dict entry 2 has incompatible type "str": "list[dict[str, dict[str, Any | str] | Any | str]]"; expected "str": "str" [dict-item] +apps/job/services/mcp_chat_service.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/job/services/month_end_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/job/services/month_end_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/job/services/month_end_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/month_end_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/quote_mode_controller.py:0: error: Cannot call function of unknown type [operator] +apps/job/services/quote_mode_controller.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/quote_mode_controller.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/quote_mode_controller.py:0: error: Incompatible return value type (got "None", expected "str") [return-value] +apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/quote_mode_controller.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/quote_mode_controller.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] +apps/job/services/quote_mode_controller.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/job/services/quote_mode_controller.py:0: note: Use "-> None" if function does not return a value +apps/job/services/quote_sync_service.py:0: error: "" has no attribute "LABOR" [attr-defined] +apps/job/services/quote_sync_service.py:0: error: "" has no attribute "MATERIAL" [attr-defined] +apps/job/services/quote_sync_service.py:0: error: Argument "quantity" to "DraftLine" has incompatible type "float | int"; expected "Decimal" [arg-type] +apps/job/services/quote_sync_service.py:0: error: Argument "unit_cost" to "DraftLine" has incompatible type "float | int"; expected "Decimal" [arg-type] +apps/job/services/quote_sync_service.py:0: error: Call to untyped function "_copy_estimate_to_quote_costset" in typed context [no-untyped-call] +apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/quote_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/quote_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/quote_sync_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/time_entry_rates.py:0: error: Incompatible return value type (got "Any | None", expected "XeroPayItem") [return-value] +apps/job/services/time_entry_rates.py:0: error: Item "None" of "Any | None" has no attribute "name" [union-attr] +apps/job/services/time_entry_rates.py:0: error: Item "None" of "Any | None" has no attribute "xero_id" [union-attr] +apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "Table" has incompatible type "list[object]"; expected "Sequence[list[Any] | tuple[Any, ...]]" [arg-type] +apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/services/workshop_pdf_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_handover_section" in typed context [no-untyped-call] +apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_handover_section" in typed context [no-untyped-call] +apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_letterhead_banner" in typed context [no-untyped-call] +apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "add_letterhead_banner" in typed context [no-untyped-call] +apps/job/services/workshop_pdf_service.py:0: error: Call to untyped function "wait_until_file_ready" in typed context [no-untyped-call] +apps/job/services/workshop_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/workshop_pdf_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_pdf_service.py:0: error: Incompatible types in assignment (expression has type "list[Table | Flowable]", variable has type "list[Table]") [assignment] +apps/job/services/workshop_pdf_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/workshop_pdf_service.py:0: error: Module "bs4" does not explicitly export attribute "NavigableString" [attr-defined] +apps/job/services/workshop_pdf_service.py:0: error: Need type annotation for "staff_hours" [var-annotated] +apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "_format_time" of "WorkshopTimesheetService" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "get_default_cost_set_summary" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/services/workshop_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/services/workshop_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/tasks.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/tasks.py:0: error: Call to untyped function "create_thumbnail" in typed context [no-untyped-call] +apps/job/tasks.py:0: error: Call to untyped function "get_thumbnail_folder" in typed context [no-untyped-call] +apps/job/tests/_pdf_golden_fixtures.py:0: error: Incompatible types in assignment (expression has type "Company", variable has type "CompanyDefaults") [assignment] +apps/job/tests/_pdf_golden_fixtures.py:0: error: Returning Any from function declared to return "Job" [no-any-return] +apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_chat_api_endpoints.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_chat_api_endpoints.py:0: error: Need type annotation for "data" (hint: "data: dict[, ] = ...") [var-annotated] apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_service.py:0: note: Use "-> None" if function does not return a value apps/job/tests/test_chat_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_chat_performance.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_performance.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_performance.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_performance.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_chat_performance.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_chat_performance.py:0: note: Use "-> None" if function does not return a value -apps/job/management/commands/test_gemini_chat.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/management/commands/test_gemini_chat.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/auth.py:0: error: Call to untyped function "RateLimitedRESTClient" in typed context [no-untyped-call] -apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "dict[str, Any] | None" [no-any-return] -apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] -apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "str" [no-any-return] -apps/workflow/api/xero/active_app.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "get_or_fetch_company" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_log_invoice_sync_events" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "recalculate_job_invoicing_state" has incompatible type "UUID"; expected "str" [arg-type] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Incompatible type for "po_number" of "PurchaseOrder" (got "Any | None", expected "str | int | Combinable") [misc] -apps/workflow/api/xero/transforms.py:0: error: Incompatible type for "order_date" of "PurchaseOrder" (got "Any | None", expected "str | date | Combinable") [misc] -apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "get" of "dict" has incompatible type "Any | None"; expected "str" [arg-type] -apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "get" of "dict" has incompatible type "Any | None"; expected "str" [arg-type] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] -apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] -apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Incompatible type for lookup 'xero_id': (got "Any | None", expected "UUID | str") [misc] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/workflow/api/xero/stock_sync.py:0: error: Missing type arguments for generic type "list" [type-arg] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "Stock") [assignment] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/stock_sync.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Call to untyped function "get_or_fetch_company" in typed context [no-untyped-call] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Call to untyped function "sync_time_entries_bulk" in typed context [no-untyped-call] -apps/workflow/api/xero/push.py:0: error: Call to untyped function "sync_expense_entries_bulk" in typed context [no-untyped-call] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] -apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] -apps/workflow/api/xero/payroll.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "date") [assignment] -apps/workflow/api/xero/payroll.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] -apps/workflow/api/xero/payroll.py:0: error: Incompatible default for parameter "payment_date" (default has type "None", parameter has type "date") [assignment] -apps/workflow/api/xero/payroll.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/workflow/api/xero/payroll.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/payroll.py:0: error: Argument "employee_id" to "post_timesheet" has incompatible type "str"; expected "UUID" [arg-type] -apps/workflow/api/xero/payroll.py:0: error: Need type annotation for "leave_ids" (hint: "leave_ids: list[] = ...") [var-annotated] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "tuple" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "tuple" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/api/xero/payroll.py:0: error: Incompatible return value type (got "list[str]", expected "int") [return-value] -apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "get_all_xero_contacts" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Unsupported operand types for + ("object" and "int") [operator] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "sync_job_to_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Unsupported operand types for + ("object" and "int") [operator] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/seed.py:0: error: Call to untyped function "_resolve_api_method" in typed context [no-untyped-call] -apps/workflow/api/xero/seed.py:0: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str" [dict-item] -apps/workflow/api/xero/seed.py:0: error: Argument 1 to "update" of "MutableMapping" has incompatible type "dict[str, str] | dict[str, bool]"; expected "SupportsKeysAndGetItem[str, str]" [arg-type] -apps/workflow/api/xero/seed.py:0: error: Incompatible types in assignment (expression has type "int", target has type "str") [assignment] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "get_last_modified_time" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "update_sync_cursor" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "get_sync_cursor" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "_resolve_api_method" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_xero_data" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_local_stock_to_xero" in typed context [no-untyped-call] -apps/workflow/api/xero/sync.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Call to untyped function "_draft" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Call to untyped function "_draft" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_chat_service.py:0: error: Incompatible default for parameter "tool_call_id" (default has type "None", parameter has type "str") [assignment] +apps/job/tests/test_chat_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/tests/test_chat_service.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/job/tests/test_chat_service.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/job/tests/test_costline_schema_validation.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/job/tests/test_costline_schema_validation.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/job/tests/test_costline_schema_validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/tests/test_event_deduplication.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] +apps/job/tests/test_event_deduplication.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] +apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_delta_rejection_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] +apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] +apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] +apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] +apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] +apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] +apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "change_id" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_after" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_after" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_checksum" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_meta" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "detail" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "detail" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "event_type" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "event_type" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "schema_version" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "staff" [union-attr] +apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] +apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] +apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] +apps/job/tests/test_job_files_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] +apps/job/tests/test_kanban_reorder_priority.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] +apps/job/tests/test_kanban_reorder_priority.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] +apps/job/tests/test_kanban_search.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] +apps/job/tests/test_kanban_search.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/tests/test_kanban_search.py:0: error: Incompatible type for "company" of "Invoice" (got "Company | None", expected "Company | Combinable") [misc] +apps/job/tests/test_kanban_search.py:0: error: Incompatible type for "company" of "Quote" (got "Company | None", expected "Company | Combinable") [misc] +apps/job/tests/test_mcp_tool_integration.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] +apps/job/tests/test_modern_timesheet_views.py:0: error: Item "None" of "XeroPayItem | None" has no attribute "name" [union-attr] +apps/job/tests/test_pdf_goldens.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/tests/test_quote_modes.py:0: error: Call to untyped function "QuoteModeController" in typed context [no-untyped-call] +apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_quote_modes.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/tests/test_workshop_pdf_service.py:0: error: Argument 1 to "convert_html_to_reportlab" has incompatible type "None"; expected "str" [arg-type] +apps/job/tests/test_workshop_timesheet_api.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/job/tests/test_workshop_timesheet_api.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] +apps/job/views/archive_completed_jobs_view.py:0: error: Call to untyped function "archive_complete_jobs" in typed context [no-untyped-call] +apps/job/views/archive_completed_jobs_view.py:0: error: Call to untyped function "get_paid_complete_jobs" in typed context [no-untyped-call] +apps/job/views/archive_completed_jobs_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/archive_completed_jobs_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/archive_completed_jobs_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] +apps/job/views/assign_job_view.py:0: error: Call to untyped function "assign_staff_to_job" of "JobStaffService" in typed context [no-untyped-call] +apps/job/views/assign_job_view.py:0: error: Call to untyped function "remove_staff_from_job" of "JobStaffService" in typed context [no-untyped-call] +apps/job/views/assign_job_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/assign_job_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/data_integrity_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/data_quality_report_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/delivery_docket_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costing_views.py:0: error: Call to untyped function "_archive_quote_data" in typed context [no-untyped-call] +apps/job/views/job_costing_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] +apps/job/views/job_costline_views.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/job/views/job_costline_views.py:0: error: Dict entry 0 has incompatible type "str": "str | _StrPromise"; expected "str": "str" [dict-item] +apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_costline_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/views/job_file_detail_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_file_detail_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_file_thumbnail_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_files_collection_view.py:0: error: "Callable[[str], None]" has no attribute "delay" [attr-defined] +apps/job/views/job_files_collection_view.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/job/views/job_files_collection_view.py:0: error: Call to untyped function "save_file" in typed context [no-untyped-call] +apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_files_collection_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_profitability_report_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_quote_chat_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: "JobQuoteChatSerializer" has no attribute "get" [attr-defined] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_message_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_message_or_404" in typed context [no-untyped-call] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: "type[Job]" has no attribute "STATUS_CHOICES" [attr-defined] +apps/job/views/job_rest_views.py:0: error: Argument 1 to "get_weekly_metrics" of "JobRestService" has incompatible type "date | None"; expected "date" [arg-type] +apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] +apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] +apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] +apps/job/views/job_rest_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] +apps/job/views/job_rest_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] +apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/job/views/job_rest_views.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/job/views/job_rest_views.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] +apps/job/views/kanban_view_api.py:0: error: "str" has no attribute "HTTP_500_INTERNAL_SERVER_ERROR" [attr-defined] +apps/job/views/kanban_view_api.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] +apps/job/views/kanban_view_api.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] +apps/job/views/kanban_view_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/kanban_view_api.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/kanban_view_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/kanban_view_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/kanban_view_api.py:0: error: Incompatible types in assignment (expression has type "list[str]", target has type "str") [assignment] +apps/job/views/modern_timesheet_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "calculated_total_cost" [union-attr] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "id" [union-attr] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "meta" [union-attr] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "quantity" [union-attr] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostLine@AnnotatedWith[TypedDict({'calculated_total_cost': Any, 'calculated_total_rev': Any})] | None" has no attribute "unit_cost" [union-attr] +apps/job/views/modern_timesheet_views.py:0: error: Item "None" of "CostSet | None" has no attribute "rev" [union-attr] +apps/job/views/month_end_rest_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/month_end_rest_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/month_end_rest_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/quote_import_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] +apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] +apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] +apps/job/views/quote_sync_views.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item] +apps/job/views/quote_sync_views.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item] +apps/job/views/quote_sync_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/quote_sync_views.py:0: error: Incompatible types in assignment (expression has type "ApplyQuoteErrorResponseSerializer", variable has type "QuoteSyncErrorResponseSerializer") [assignment] +apps/job/views/quote_sync_views.py:0: error: Incompatible types in assignment (expression has type "ApplyQuoteErrorResponseSerializer", variable has type "QuoteSyncErrorResponseSerializer") [assignment] +apps/job/views/workshop_pdf_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/job/views/workshop_view.py:0: error: Incompatible type for lookup 'people__id': (got "UUID | None", expected "UUID | str") [misc] +apps/job/views/workshop_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] +apps/job/views/workshop_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/serializers/workshop_schedule_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/operations/services/scheduler_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] +apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] +apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] +apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] +apps/operations/services/scheduler_service.py:0: error: Invalid index type "UUID" for "dict[int, float]"; expected type "int" [index] +apps/operations/services/scheduler_service.py:0: error: Missing type arguments for generic type "frozenset" [type-arg] +apps/operations/tests/test_scheduler_persistence.py:0: error: Item "None" of "SchedulerRun | None" has no attribute "id" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertGreater" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderGT[Any]" [arg-type] +apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertGreater" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderGT[Any]" [arg-type] +apps/operations/tests/test_scheduler_service.py:0: error: Argument 1 to "assertLessEqual" of "TestCase" has incompatible type "date | None"; expected "SupportsDunderLE[Any]" [arg-type] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_book_time" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_book_time" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_make_special_leave_job" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_make_special_leave_job" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_set_efficiency" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Call to untyped function "_set_efficiency" in typed context [no-untyped-call] +apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertGreater" of "TestCase" [misc] +apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertGreater" of "TestCase" [misc] +apps/operations/tests/test_scheduler_service.py:0: error: Cannot infer value of type parameter "_T" of "assertLessEqual" of "TestCase" [misc] +apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/operations/tests/test_scheduler_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/operations/tests/test_scheduler_service.py:0: error: Incompatible type for lookup 'allocation_date': (got "date | None", expected "str | date") [misc] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/tests/test_scheduler_service.py:0: error: Item "None" of "AllocationBlock | None" has no attribute "allocation_date" [union-attr] +apps/operations/views/workshop_schedule_view.py:0: error: Incompatible types in assignment (expression has type "list[JobProjection]", variable has type "QuerySet[JobProjection, JobProjection]") [assignment] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Argument 3 to "_upload_to_google_docs" of "Command" has incompatible type "str | None"; expected "str" [arg-type] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Call to untyped function "_get_drive_service" in typed context [no-untyped-call] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Call to untyped function "from_service_account_file" of "Credentials" in typed context [no-untyped-call] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Incompatible types in assignment (expression has type "Procedure", variable has type "Form") [assignment] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "tuple" [type-arg] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Missing type arguments for generic type "tuple" [type-arg] +apps/process/management/commands/import_dropbox_hs_documents.py:0: error: Need type annotation for "candidates" (hint: "candidates: dict[, ] = ...") [var-annotated] +apps/process/serializers/form_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/serializers/procedure_serializer.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/services/form_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/process/services/form_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/google_docs_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/process/services/google_docs_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/process/services/google_docs_service.py:0: note: Use "-> None" if function does not return a value +apps/process/services/procedure_service.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] +apps/process/services/procedure_service.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] +apps/process/services/procedure_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/services/procedure_service.py:0: note: Use "-> None" if function does not return a value +apps/process/services/safety_ai_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] +apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] +apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] +apps/process/services/safety_ai_service.py:0: error: Incompatible return value type (got "dict[Any, Any] | str", expected "dict[str, Any]") [return-value] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "get" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Item "str" of "dict[Any, Any] | str" has no attribute "setdefault" [union-attr] +apps/process/services/safety_ai_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "list[dict[str, str]]" [no-any-return] +apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "list[str]" [no-any-return] +apps/process/services/safety_ai_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/process/services/safety_ai_service.py:0: note: Use "-> None" if function does not return a value +apps/process/tests/conftest.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/conftest.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_model.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_form_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/tests/test_procedure_model.py:0: error: Item "None" of "Procedure | None" has no attribute "title" [union-attr] +apps/process/tests/test_procedure_service.py:0: error: "Callable[[str, str], GoogleDocResult]" has no attribute "assert_called_once_with" [attr-defined] +apps/process/tests/test_procedure_service.py:0: error: "Callable[[str, str], GoogleDocResult]" has no attribute "return_value" [attr-defined] +apps/process/tests/test_procedure_service.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] +apps/process/urls_rest.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Argument "document_type" to "create_form" of "FormService" has incompatible type "Sequence[str]"; expected "str" [arg-type] +apps/process/views/form_viewsets.py:0: error: Call to untyped function "_apply_category_filter" in typed context [no-untyped-call] +apps/process/views/form_viewsets.py:0: error: Call to untyped function "_apply_common_filters" in typed context [no-untyped-call] +apps/process/views/form_viewsets.py:0: error: Call to untyped function "get_form" in typed context [no-untyped-call] +apps/process/views/form_viewsets.py:0: error: Call to untyped function "update" of "FormViewSet" in typed context [no-untyped-call] +apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/form_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] +apps/process/views/form_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Argument "document_type" to "create_blank_procedure" of "ProcedureService" has incompatible type "Sequence[str]"; expected "str" [arg-type] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "GoogleDocsService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "ProcedureService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "SafetyAIService" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_apply_category_filter" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_apply_common_filters" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_get_procedure" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "_get_procedure" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Call to untyped function "update" of "ProcedureViewSet" in typed context [no-untyped-call] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/process/views/procedure_viewsets.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "Mapping[str, Any]") [assignment] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/process/views/procedure_viewsets.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/models.py:0: error: "PurchaseOrderLine" has no attribute "received_lines" [attr-defined] +apps/purchasing/models.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/purchasing/models.py:0: error: Call to untyped function "generate_po_number" in typed context [no-untyped-call] +apps/purchasing/models.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/serializers.py:0: error: Argument "default" to "DecimalField" has incompatible type "int"; expected "Decimal | Callable[[], Decimal] | _Empty | None" [arg-type] +apps/purchasing/serializers.py:0: error: Argument "default" to "DecimalField" has incompatible type "int"; expected "Decimal | Callable[[], Decimal] | _Empty | None" [arg-type] +apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | None") [assignment] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "delete" in typed context [no-untyped-call] +apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/purchasing/services/allocation_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/services/allocation_service.py:0: error: Function "builtins.any" is not valid as a type [valid-type] +apps/purchasing/services/allocation_service.py:0: error: Incompatible return value type (got "tuple[PurchaseOrderLine | None, Stock]", expected "tuple[PurchaseOrderLine, Stock | CostLine]") [return-value] +apps/purchasing/services/allocation_service.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] +apps/purchasing/services/allocation_service.py:0: note: Perhaps you meant "typing.Any" instead of "any"? +apps/purchasing/services/delivery_receipt_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/purchasing/services/delivery_receipt_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/delivery_receipt_service.py:0: error: Incompatible type for "unit_cost" of "CostLine" (got "Decimal | None", expected "str | float | Decimal | Combinable") [misc] +apps/purchasing/services/delivery_receipt_service.py:0: error: Incompatible type for "unit_cost" of "Stock" (got "Decimal | None", expected "str | float | Decimal | Combinable") [misc] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/purchasing/services/delivery_receipt_service.py:0: error: Incompatible type for "unit_cost" of "Stock" (got "Decimal | None", expected "str | float | Decimal | Combinable") [misc] +apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] apps/purchasing/services/delivery_receipt_service.py:0: error: Unexpected attribute "retail_rate" for model "Stock" [misc] -apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] apps/purchasing/services/delivery_receipt_service.py:0: error: Unsupported operand types for * ("None" and "Decimal") [operator] apps/purchasing/services/delivery_receipt_service.py:0: note: Left operand is of type "Decimal | None" -apps/purchasing/services/delivery_receipt_service.py:0: error: Incompatible type for "unit_cost" of "CostLine" (got "Decimal | None", expected "str | float | Decimal | Combinable") [misc] -apps/purchasing/services/delivery_receipt_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/services/delivery_receipt_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/delivery_receipt_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/purchasing/services/delivery_receipt_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/accounting/views/kpi_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: "SalesPipelineServiceFixturesMixin" has no attribute "test_staff" [attr-defined] -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/accounting/tests/test_sales_pipeline_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounting/tests/test_sales_pipeline_service.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_access_logging_middleware.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_access_logging_middleware.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/stock_search_rest_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_email_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "PurchaseOrderPDFGenerator" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_header_info" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_line_items_table" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_logo" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "add_supplier_info" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Call to untyped function "generate" in typed context [no-untyped-call] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: List item 0 has incompatible type "Paragraph"; expected "str" [list-item] +apps/purchasing/services/purchase_order_pdf_service.py:0: error: List item 1 has incompatible type "Paragraph"; expected "str" [list-item] +apps/purchasing/services/purchasing_rest_service.py:0: error: Argument 2 to "_create_line" of "PurchasingRestService" has incompatible type "Any | None"; expected "str" [arg-type] +apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] +apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function (unknown) in typed context [no-untyped-call] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/purchasing_rest_service.py:0: error: Incompatible return value type (got "QuerySet[Stock, Stock]", expected "list[dict[str, Any]]") [return-value] +apps/purchasing/services/purchasing_rest_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/purchasing/services/purchasing_rest_service.py:0: error: Need type annotation for "updated_line_ids" (hint: "updated_line_ids: set[] = ...") [var-annotated] +apps/purchasing/services/quote_to_po_service.py:0: error: Argument "contents" to "generate_content" of "Models" has incompatible type "list[dict[str, str]]"; expected "Content | ContentDict | str | Image | File | FileDict | Part | PartDict | list[str | Image | File | FileDict | Part | PartDict] | list[Content | ContentDict | str | Image | File | FileDict | Part | PartDict | list[str | Image | File | FileDict | Part | PartDict]]" [arg-type] +apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "clean_json_response" has incompatible type "str | None"; expected "str" [arg-type] +apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/purchasing/services/quote_to_po_service.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/purchasing/services/quote_to_po_service.py:0: error: Call to untyped function "log_token_usage" in typed context [no-untyped-call] +apps/purchasing/services/quote_to_po_service.py:0: error: Dict entry 0 has incompatible type "str": "dict[str, str]"; expected "str": "str" [dict-item] +apps/purchasing/services/quote_to_po_service.py:0: error: Dict entry 0 has incompatible type "str": "dict[str, str]"; expected "str": "str" [dict-item] +apps/purchasing/services/quote_to_po_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/quote_to_po_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/services/stock_search_service.py:0: error: Argument 1 to "add" of "set" has incompatible type "tuple[float, ...]"; expected "tuple[float, float]" [arg-type] +apps/purchasing/services/stock_search_service.py:0: error: Argument 1 to "add" of "set" has incompatible type "tuple[float, ...]"; expected "tuple[float, float]" [arg-type] +apps/purchasing/services/stock_search_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/services/stock_search_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/services/stock_search_service.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[str, Any]]") [return-value] +apps/purchasing/services/stock_search_service.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Stock, Stock]", variable has type "list[Stock]") [assignment] +apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/services/stock_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/purchasing/tests/test_serializers.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] +apps/purchasing/tests/test_serializers.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: note: Use "-> None" if function does not return a value -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] apps/purchasing/tests/test_stock_fts_search.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/tests/test_stock_fts_search.py:0: error: Call to untyped function "_stock" in typed context [no-untyped-call] -apps/job/tests/test_modern_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_modern_timesheet_views.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_modern_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_modern_timesheet_views.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_modern_timesheet_views.py:0: error: Item "None" of "XeroPayItem | None" has no attribute "name" [union-attr] -apps/job/views/kanban_view_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/kanban_view_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/kanban_view_api.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] -apps/job/views/kanban_view_api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/kanban_view_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/kanban_view_api.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] -apps/job/views/kanban_view_api.py:0: error: "str" has no attribute "HTTP_500_INTERNAL_SERVER_ERROR" [attr-defined] -apps/job/views/kanban_view_api.py:0: error: Incompatible types in assignment (expression has type "list[str]", target has type "str") [assignment] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: "JobQuoteChatSerializer" has no attribute "get" [attr-defined] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_message_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_message_or_404" in typed context [no-untyped-call] -apps/job/views/job_quote_chat_views.py:0: error: Call to untyped function "get_job_or_404_response" in typed context [no-untyped-call] -apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costing_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_costing_views.py:0: error: Call to untyped function "_archive_quote_data" in typed context [no-untyped-call] -apps/job/views/job_costing_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/assign_job_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/assign_job_view.py:0: error: Call to untyped function "assign_staff_to_job" of "JobStaffService" in typed context [no-untyped-call] -apps/job/views/assign_job_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/assign_job_view.py:0: error: Call to untyped function "remove_staff_from_job" of "JobStaffService" in typed context [no-untyped-call] -apps/job/services/job_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/job_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/services/job_rest_service.py:0: error: Incompatible return value type (got "ReturnDict[Any, Any]", expected "list[dict[str, Any]]") [return-value] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: Value of type "JobDeltaRejection@AnnotatedWith[TypedDict({'occurrence_count': int, 'first_seen': Any, 'last_seen': Any, 'latest_id': Any})]" is not indexable [index] -apps/job/services/job_rest_service.py:0: error: "ValueError" has no attribute "delta_rejection_context" [attr-defined] -apps/job/services/job_rest_service.py:0: error: Assignment to variable "exc" outside except: block [misc] -apps/job/services/job_rest_service.py:0: error: Trying to read deleted variable "exc" [misc] -apps/job/services/job_rest_service.py:0: error: Trying to read deleted variable "exc" [misc] -apps/job/services/job_rest_service.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] -apps/job/services/job_rest_service.py:0: error: Argument "key" to "sort" of "list" has incompatible type "Callable[[dict[str, int | datetime | UUID | str | Any | None]], int | datetime | UUID | str | Any | None]"; expected "Callable[[dict[str, int | datetime | UUID | str | Any | None]], SupportsDunderLT[Any] | SupportsDunderGT[Any]]" [arg-type] -apps/job/services/job_rest_service.py:0: error: Incompatible return value type (got "int | datetime | UUID | str | Any | None", expected "SupportsDunderLT[Any] | SupportsDunderGT[Any]") [return-value] -apps/job/services/job_rest_service.py:0: error: Incompatible default for parameter "week" (default has type "None", parameter has type "date") [assignment] -apps/job/services/job_rest_service.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True -apps/job/services/job_rest_service.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase -apps/job/services/job_rest_service.py:0: error: "Job" has no attribute "pricings" [attr-defined] -apps/job/views/job_quote_chat_api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] -apps/workflow/views/xero_apps_view.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "transform_quote" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "transform_purchase_order" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_tenant_id.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_tenant_id.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_tenant_id.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_tenant_id.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_tenant_id.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_tenant_id.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] -apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] -apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_create_response" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_create_response" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "create_company_contact_in_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_create_response" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] -apps/workflow/services/xero_sync_service.py:0: error: "Callable[[str], None]" has no attribute "delay" [attr-defined] -apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/services/xero_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/start_xero_sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/start_xero_sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Too many arguments for "get_authentication_url" [call-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "create_company_contact_in_xero" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] -apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/accounting/xero/provider.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] -apps/timesheet/views/api.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/timesheet/views/api.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/tests/test_supplier_search.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] +apps/purchasing/views/purchasing_rest_views.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_match" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_match" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_none_match" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_precondition_required_response" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_precondition_required_response" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "create_purchase_order_pdf" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "update_xero_status" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "update_xero_status" in typed context [no-untyped-call] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/purchasing/views/purchasing_rest_views.py:0: error: Generator has incompatible item type "int"; expected "bool" [misc] +apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] +apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] +apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "PurchaseOrderLine | None" has no attribute "id" [union-attr] +apps/purchasing/views/purchasing_rest_views.py:0: error: Need type annotation for "allocations_by_line" (hint: "allocations_by_line: dict[, ] = ...") [var-annotated] +apps/purchasing/views/stock_search_rest_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/purchasing/views/supplier_search_rest_view.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/quoting/management/commands/run_scrapers.py:0: error: Argument 5 to "run_scraper" of "Command" has incompatible type "Any | None"; expected "bool" [arg-type] +apps/quoting/management/commands/run_scrapers.py:0: error: Call to untyped function "get_available_scrapers" in typed context [no-untyped-call] +apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/management/commands/run_scrapers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/quoting/mcp.py:0: error: Class cannot subclass "MCPToolset" (has type "Any") [misc] +apps/quoting/mcp.py:0: error: Class cannot subclass "ModelQueryToolset" (has type "Any") [misc] +apps/quoting/mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/mcp.py:0: error: Incompatible default for parameter "dimensions" (default has type "None", parameter has type "str") [assignment] +apps/quoting/mcp.py:0: error: Incompatible default for parameter "labor_hours" (default has type "None", parameter has type "float") [assignment] +apps/quoting/mcp.py:0: error: Incompatible default for parameter "supplier_name" (default has type "None", parameter has type "str") [assignment] +apps/quoting/mcp.py:0: error: Incompatible default for parameter "supplier_name" (default has type "None", parameter has type "str") [assignment] +apps/quoting/mcp.py:0: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment] +apps/quoting/mcp.py:0: error: Item "None" of "Company | None" has no attribute "name" [union-attr] +apps/quoting/mcp.py:0: error: Need type annotation for "supplier_prices" (hint: "supplier_prices: dict[, ] = ...") [var-annotated] +apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/quoting/mcp.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/quoting/mcp.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/quoting/scrapers/base.py:0: error: Call to untyped function "cleanup" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "get_product_urls" of "BaseScraper" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "login" of "BaseScraper" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "save_products" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "save_products" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "scrape_product" of "BaseScraper" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Call to untyped function "setup_driver" in typed context [no-untyped-call] +apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/quoting/scrapers/base.py:0: error: Incompatible types in assignment (expression has type "WebDriver", variable has type "None") [assignment] +apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value +apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value +apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value +apps/quoting/scrapers/base.py:0: note: Use "-> None" if function does not return a value +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "current_url" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "execute_script" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_element" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "find_elements" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "get" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: "None" has no attribute "page_source" [attr-defined] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], Literal[False] | WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement | bool]"; expected "Callable[[None], object]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Argument 1 to "until" of "WebDriverWait" has incompatible type "Callable[[WebDriver | WebElement], WebElement]"; expected "Callable[[None], Literal[False] | WebElement]" [arg-type] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_all_variants" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_description" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_price_unit" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_specifications" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_text_by_selector" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_text_by_selector" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_variants_direct" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "extract_variants_for_width" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "is_product_url" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Call to untyped function "login" in typed context [no-untyped-call] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/scrapers/steel_and_tube.py:0: error: Value of type variable "D" of "WebDriverWait" cannot be "None" [type-var] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/quoting/services/ai_price_extraction.py:0: error: Call to untyped function "get_prioritized_active_providers" in typed context [no-untyped-call] +apps/quoting/services/ai_price_extraction.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/services/ai_price_extraction.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "int" [call-overload] +apps/quoting/services/ai_price_extraction.py:0: note: Possible overload variants: +apps/quoting/services/ai_price_extraction.py:0: note: def [_T] get(self, AIProviderTypes, _T, /) -> int | _T +apps/quoting/services/ai_price_extraction.py:0: note: def get(self, AIProviderTypes, None = ..., /) -> int | None +apps/quoting/services/ai_price_extraction.py:0: note: def get(self, AIProviderTypes, int, /) -> int +apps/quoting/services/pdf_data_validation.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/quoting/services/pdf_import_service.py:0: error: "object" has no attribute "append" [attr-defined] +apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/services/pdf_import_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/services/pdf_import_service.py:0: error: Incompatible return value type (got "None", expected "SupplierProduct") [return-value] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/quoting/services/pdf_import_service.py:0: note: Use "-> None" if function does not return a value +apps/quoting/services/pdf_import_service.py:0: note: Use "-> None" if function does not return a value +apps/quoting/services/providers/common.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/services/providers/gemini_provider.py:0: error: Argument "config" to "generate_content" of "Models" has incompatible type "dict[str, object]"; expected "GenerateContentConfig | GenerateContentConfigDict | None" [arg-type] +apps/quoting/services/providers/gemini_provider.py:0: error: Call to untyped function "log_token_usage" in typed context [no-untyped-call] +apps/quoting/services/providers/gemini_provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "Any | None", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "int", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "int", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Incompatible types in assignment (expression has type "list[dict[str, object]]", target has type "Sequence[str]") [assignment] +apps/quoting/services/providers/gemini_provider.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/quoting/services/providers/gemini_provider.py:0: error: Need type annotation for "supplier_info" (hint: "supplier_info: dict[, ] = ...") [var-annotated] +apps/quoting/services/providers/mistral_provider.py:0: error: Argument 1 to "_parse_price_from_string" of "MistralPriceExtractionProvider" has incompatible type "str | None"; expected "str" [arg-type] +apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "encode_pdf" in typed context [no-untyped-call] +apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] +apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] +apps/quoting/services/providers/mistral_provider.py:0: error: Call to untyped function "ocr_response_to_dict" in typed context [no-untyped-call] +apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/services/providers/mistral_provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/quoting/tests/test_ocr_fixtures.py:0: error: Call to untyped function "_ocr_response" in typed context [no-untyped-call] +apps/quoting/tests/test_ocr_fixtures.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests/test_ocr_fixtures.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Call to untyped function "get_queryset" in typed context [no-untyped-call] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_mcp.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/tests_utils.py:0: note: Use "-> None" if function does not return a value +apps/quoting/views.py:0: error: Call to untyped function "PDFImportService" in typed context [no-untyped-call] +apps/quoting/views.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/quoting/views_scheduled_tasks.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] +apps/quoting/views_scheduled_tasks.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] +apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] +apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] +apps/testing.py:0: error: Call to untyped function "_create_test_staff" in typed context [no-untyped-call] +apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] +apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] +apps/testing.py:0: error: Call to untyped function "_ensure_test_media_files" in typed context [no-untyped-call] +apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/testing.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/testing.py:0: note: Use "-> None" if function does not return a value +apps/testing.py:0: note: Use "-> None" if function does not return a value +apps/testing.py:0: note: Use "-> None" if function does not return a value +apps/testing.py:0: note: Use "-> None" if function does not return a value +apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/management/commands/create_leave_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/create_leave_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Call to untyped function "_do_preview" in typed context [no-untyped-call] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "accounting_date" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | date | Combinable") [misc] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "cost_set" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "CostSet | Combinable") [misc] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "quantity" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | float | Decimal | Combinable") [misc] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible type for "unit_cost" of "CostLine" (got "date | Decimal | CostSet | Staff | str | Any", expected "str | float | Decimal | Combinable") [misc] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "date | Decimal | CostSet | Staff | str | Any", variable has type "Staff") [assignment] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Invalid index type "str | Any | date | Decimal | CostSet | Staff" for "dict[str, int]"; expected type "str" [index] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "CostSet" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Decimal" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "Staff" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "date" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "split" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "date" of "str | Any | date | Decimal | CostSet | Staff" has no attribute "startswith" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Item "str" of "date | Decimal | CostSet | Staff | str | Any" has no attribute "isoformat" [union-attr] +apps/timesheet/management/commands/create_overtime_entries.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/timesheet/management/commands/create_special_job.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/create_special_job.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/reassign_time_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/timesheet/management/commands/reassign_time_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/reassign_time_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Argument 4 to "_split_costline" of "Command" has incompatible type "Decimal | CostLine | str | Any"; expected "str" [arg-type] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "_do_preview" in typed context [no-untyped-call] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "Decimal | CostLine | str | Any", variable has type "CostLine") [assignment] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: error: Incompatible types in assignment (expression has type "Decimal | CostLine | str | Any", variable has type "Decimal") [assignment] +apps/timesheet/management/commands/reclassify_overtime_entries.py:0: note: Use "-> None" if function does not return a value +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/daily_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/modern_timesheet_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/serializers/payroll_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "int"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 1 to "_determine_status" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_calculate_percentage" of "DailyTimesheetService" has incompatible type "int"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Argument 2 to "_get_staff_alerts" of "DailyTimesheetService" has incompatible type "Decimal | Literal[0]"; expected "Decimal" [arg-type] +apps/timesheet/services/daily_timesheet_service.py:0: error: Call to untyped function "ensure_json_serializable" in typed context [no-untyped-call] +apps/timesheet/services/daily_timesheet_service.py:0: error: Call to untyped function "ensure_json_serializable" in typed context [no-untyped-call] +apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/services/daily_timesheet_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/daily_timesheet_service.py:0: error: Missing type arguments for generic type "Dict" [type-arg] +apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] +apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] +apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] +apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] +apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] +apps/timesheet/services/payroll_employee_sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/services/payroll_employee_sync.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] +apps/timesheet/services/weekly_timesheet_service.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/timesheet/services/weekly_timesheet_service.py:0: error: Need type annotation for "lines_by_staff_day" (hint: "lines_by_staff_day: dict[, ] = ...") [var-annotated] +apps/timesheet/services/xero_hours.py:0: error: Incompatible return value type (got "dict[str | None, Staff]", expected "dict[str, Staff]") [return-value] +apps/timesheet/services/xero_hours.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/timesheet/services/xero_hours.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/timesheet/tests/test_daily_timesheet_service.py:0: error: Returning Any from function declared to return "Job" [no-any-return] +apps/timesheet/tests/test_jobs_api_filter.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] +apps/timesheet/tests/test_jobs_api_filter.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/tests/test_jobs_api_filter.py:0: error: Returning Any from function declared to return "Job" [no-any-return] +apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] +apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] +apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] +apps/timesheet/tests/test_payroll_post_api.py:0: error: "_MonkeyPatchedResponse" has no attribute "streaming_content" [attr-defined] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/tests/test_payroll_post_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/timesheet/views/api.py:0: error: Call to untyped function "build_timesheet_response" in typed context [no-untyped-call] +apps/timesheet/views/api.py:0: error: Call to untyped function "generate_payroll_events" in typed context [no-untyped-call] +apps/timesheet/views/api.py:0: error: Function is missing a return type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/views/api.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/views/api.py:0: error: Call to untyped function "build_timesheet_response" in typed context [no-untyped-call] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] apps/timesheet/views/api.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/views/api.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/views/api.py:0: error: Call to untyped function "generate_payroll_events" in typed context [no-untyped-call] -apps/timesheet/services/payroll_employee_sync.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] -apps/timesheet/services/payroll_employee_sync.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] -apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] -apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] -apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] -apps/timesheet/services/payroll_employee_sync.py:0: error: Call to untyped function "_hours_between" in typed context [no-untyped-call] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function (unknown) in typed context [no-untyped-call] -apps/purchasing/services/purchasing_rest_service.py:0: error: Argument 2 to "_create_line" of "PurchasingRestService" has incompatible type "Any | None"; expected "str" [arg-type] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/services/purchasing_rest_service.py:0: error: Need type annotation for "updated_line_ids" (hint: "updated_line_ids: set[] = ...") [var-annotated] -apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] -apps/purchasing/services/purchasing_rest_service.py:0: error: Incompatible return value type (got "QuerySet[Stock, Stock]", expected "list[dict[str, Any]]") [return-value] -apps/purchasing/services/purchasing_rest_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/purchasing/services/purchasing_rest_service.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] -apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Call to untyped function "get_serializer_class" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Argument 1 to "get_weekly_metrics" of "JobRestService" has incompatible type "date | None"; expected "date" [arg-type] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Call to untyped function "calculate_profit_margin" in typed context [no-untyped-call] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: "type[Job]" has no attribute "STATUS_CHOICES" [attr-defined] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/job_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_rest_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_rest_service.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_rest_service.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_delta_rejection_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] -apps/job/tests/test_event_deduplication.py:0: error: Call to untyped function "create_safe" of "JobEvent" in typed context [no-untyped-call] -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_event_deduplication.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_event_deduplication.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tasks.py:0: error: Call to untyped function "sync_single_contact" in typed context [no-untyped-call] -apps/workflow/tasks.py:0: error: Call to untyped function "sync_single_invoice" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Argument 1 to "exchange_code_for_token" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "exchange_code_for_token" has incompatible type "str | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument 3 to "exchange_code_for_token" has incompatible type "Any | None"; expected "str" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Returning Any from function declared to return "HttpResponse" [no-any-return] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_active_task_id" of "XeroSyncService" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_active_task_id" of "XeroSyncService" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_messages" of "XeroSyncService" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_messages" of "XeroSyncService" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible return value type (got "JsonResponse", expected "StreamingHttpResponse") [return-value] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "generate_xero_sync_events" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Argument "company" to "XeroInvoiceManager" has incompatible type "Company | None"; expected "Company" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroInvoiceManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroPurchaseOrderManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentSuccessResponseSerializer", variable has type "XeroDocumentErrorResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Argument "company" to "XeroInvoiceManager" has incompatible type "Company | None"; expected "Company" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroInvoiceManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument "xero_invoice_id" to "XeroInvoiceManager" has incompatible type "UUID"; expected "str | None" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroPurchaseOrderManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] -apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "_get_last_sync_time" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "_get_last_sync_time" in typed context [no-untyped-call] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] -apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "RetrieveAPIView" [type-arg] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "str") [assignment] +apps/timesheet/views/api.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/views/api.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/timesheet/views/api.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/workflow/accounting/provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/types.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/types.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] +apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] +apps/workflow/accounting/types.py:0: error: Name "date" is not defined [name-defined] +apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.InvoicePayload.date" is not valid as a type [valid-type] +apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.POPayload.date" is not valid as a type [valid-type] +apps/workflow/accounting/types.py:0: error: Variable "apps.workflow.accounting.types.QuotePayload.date" is not valid as a type [valid-type] +apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +apps/workflow/accounting/types.py:0: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_build_line_items" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_make_error_result" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "_to_xero_payload" of "XeroAccountingProvider" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "create_company_contact_in_xero" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] +apps/workflow/accounting/xero/provider.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/accounting/xero/provider.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/accounting/xero/provider.py:0: error: Incompatible return value type (got "str | None", expected "str") [return-value] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Returning Any from function declared to return "DocumentResult" [no-any-return] +apps/workflow/accounting/xero/provider.py:0: error: Too many arguments for "get_authentication_url" [call-arg] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_comparison_metrics" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_draft_conversion_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "calculate_quote_acceptance_rate" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_draft_jobs_created" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_by_status_path" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_jobs_won" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_accepted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "get_quotes_submitted" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_event_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_event_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Call to untyped function "serialize_job_list" in typed context [no-untyped-call] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/job_movement.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/api/reports/job_movement.py:0: error: Need type annotation for "jobs_through_quotes" (hint: "jobs_through_quotes: set[] = ...") [var-annotated] +apps/workflow/api/reports/payroll_reconciliation.py:0: error: Module "drf_spectacular.utils" does not explicitly export attribute "OpenApiTypes" [attr-defined] +apps/workflow/api/reports/pnl.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/reports/utils.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/active_app.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/auth.py:0: error: Call to untyped function "RateLimitedRESTClient" in typed context [no-untyped-call] +apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/auth.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "dict[str, Any] | None" [no-any-return] +apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "dict[str, Any]" [no-any-return] +apps/workflow/api/xero/auth.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_handle_rate_limit" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_log_quota" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_log_summary" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_maybe_log_threshold_warning" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_maybe_log_threshold_warning" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_parse_int" of "RateLimitedRESTClient" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_record_quota" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] +apps/workflow/api/xero/client.py:0: error: Class cannot subclass "RESTClientObject" (has type "Any") [misc] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/client.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Argument "employee_id" to "post_timesheet" has incompatible type "str"; expected "UUID" [arg-type] +apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] +apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] +apps/workflow/api/xero/payroll.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/payroll.py:0: error: Incompatible default for parameter "payment_date" (default has type "None", parameter has type "date") [assignment] +apps/workflow/api/xero/payroll.py:0: error: Incompatible return value type (got "list[str]", expected "int") [return-value] +apps/workflow/api/xero/payroll.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "date") [assignment] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "List" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "tuple" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Missing type arguments for generic type "tuple" [type-arg] +apps/workflow/api/xero/payroll.py:0: error: Need type annotation for "leave_ids" (hint: "leave_ids: list[] = ...") [var-annotated] +apps/workflow/api/xero/payroll.py:0: error: Returning Any from function declared to return "date | None" [no-any-return] +apps/workflow/api/xero/payroll.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True +apps/workflow/api/xero/payroll.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase +apps/workflow/api/xero/push.py:0: error: Call to untyped function "get_or_fetch_company" in typed context [no-untyped-call] +apps/workflow/api/xero/push.py:0: error: Call to untyped function "sync_expense_entries_bulk" in typed context [no-untyped-call] +apps/workflow/api/xero/push.py:0: error: Call to untyped function "sync_time_entries_bulk" in typed context [no-untyped-call] +apps/workflow/api/xero/push.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/push.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Argument after ** must have string keys [arg-type] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_bills" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_companies" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_credit_notes" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "reprocess_invoices" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/reprocess_xero.py:0: error: Item "None" of "type[InvoiceLineItem] | type[BillLineItem] | type[CreditNoteLineItem] | None" has no attribute "objects" [union-attr] +apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/api/xero/reprocess_xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/api/xero/seed.py:0: error: Argument 1 to "update" of "MutableMapping" has incompatible type "dict[str, str] | dict[str, bool]"; expected "SupportsKeysAndGetItem[str, str]" [arg-type] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "_resolve_api_method" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "get_all_xero_contacts" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "sync_job_to_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Call to untyped function "transform_pay_run" in typed context [no-untyped-call] +apps/workflow/api/xero/seed.py:0: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str" [dict-item] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/seed.py:0: error: Incompatible types in assignment (expression has type "int", target has type "str") [assignment] +apps/workflow/api/xero/seed.py:0: error: Unsupported operand types for + ("object" and "int") [operator] +apps/workflow/api/xero/seed.py:0: error: Unsupported operand types for + ("object" and "int") [operator] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/stock_sync.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/stock_sync.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/stock_sync.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "Stock") [assignment] +apps/workflow/api/xero/stock_sync.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/workflow/api/xero/stock_sync.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "_resolve_api_method" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "get_last_modified_time" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "get_sync_cursor" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_entities" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "sync_local_stock_to_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Call to untyped function "update_sync_cursor" in typed context [no-untyped-call] +apps/workflow/api/xero/sync.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/sync.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "get" of "dict" has incompatible type "Any | None"; expected "str" [arg-type] +apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "get" of "dict" has incompatible type "Any | None"; expected "str" [arg-type] +apps/workflow/api/xero/transforms.py:0: error: Argument 1 to "recalculate_job_invoicing_state" has incompatible type "UUID"; expected "str" [arg-type] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "_log_invoice_sync_events" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "clean_json" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "get_or_fetch_company" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "serialize_xero_object" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "set_invoice_or_bill_fields" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Call to untyped function "sync_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/api/xero/transforms.py:0: error: Incompatible type for "order_date" of "PurchaseOrder" (got "Any | None", expected "str | date | Combinable") [misc] +apps/workflow/api/xero/transforms.py:0: error: Incompatible type for "po_number" of "PurchaseOrder" (got "Any | None", expected "str | int | Combinable") [misc] +apps/workflow/api/xero/transforms.py:0: error: Incompatible type for lookup 'xero_id': (got "Any | None", expected "UUID | str") [misc] +apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] +apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] +apps/workflow/api/xero/transforms.py:0: error: Item "None" of "Any | None" has no attribute "date" [union-attr] +apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/workflow/api/xero/transforms.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/workflow/apps.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/apps.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/authentication.py:0: error: "AbstractBaseUser" has no attribute "is_currently_active" [attr-defined] +apps/workflow/authentication.py:0: error: Call to untyped function "get_raw_token_from_cookie" in typed context [no-untyped-call] +apps/workflow/authentication.py:0: error: Call to untyped function "mark_used" in typed context [no-untyped-call] +apps/workflow/authentication.py:0: error: Call to untyped function "mark_used" in typed context [no-untyped-call] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/authentication.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/authentication.py:0: error: Returning Any from function declared to return "bool" [no-any-return] +apps/workflow/exception_handlers.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/extensions.py:0: error: Call to untyped function "__init_subclass__" in typed context [no-untyped-call] +apps/workflow/extensions.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/management/commands/backfill_kanban_search_telemetry.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run_pipe" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "analyze_fields" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "cannot_be_pii" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "is_uuid_string" in typed context [no-untyped-call] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/backport_data_backup.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "str") [assignment] +apps/workflow/management/commands/backport_data_backup.py:0: error: Item "None" of "IO[Any] | None" has no attribute "close" [union-attr] +apps/workflow/management/commands/backport_data_backup.py:0: error: Need type annotation for "field_samples" [var-annotated] +apps/workflow/management/commands/create_service_api_key.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/create_service_api_key.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/e2e_cleanup.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/e2e_cleanup.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_reset_scrub_schema" in typed context [no-untyped-call] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_run_pipe" in typed context [no-untyped-call] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_reset_scrub_schema" in typed context [no-untyped-call] -apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Call to untyped function "_run_pipe" in typed context [no-untyped-call] apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/export_dev_demo_dump.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "str") [assignment] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_batch_create_invoices" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_batch_create_quotes" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_build_invoice_payload" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_build_quote_payload" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_get_invoice_line_unit_amount" of "Command" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "clear_production_xero_ids" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_accounts" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "fetch_xero_entity_lookup" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "fetch_xero_entity_lookup" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_contacts" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_projects" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_employees" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_invoices" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_projects" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_quotes" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_stock_items" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "process_xero_data" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "seed_companies_to_xero" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "seed_jobs_to_xero" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "fetch_xero_entity_lookup" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_batch_create_invoices" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_get_invoice_line_unit_amount" of "Command" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_build_invoice_payload" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "fetch_xero_entity_lookup" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_batch_create_quotes" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_build_quote_payload" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Item "None" of "str | None" has no attribute "endswith" [union-attr] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_column_exists" in typed context [no-untyped-call] -apps/workflow/management/commands/seed_xero_from_database.py:0: error: Call to untyped function "_table_exists" in typed context [no-untyped-call] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_users" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_employees" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_rates" in typed context [no-untyped-call] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/seed_xero_from_database.py:0: error: Item "None" of "str | None" has no attribute "endswith" [union-attr] +apps/workflow/management/commands/start_xero_sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/start_xero_sync.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/sync_sequences.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "_print_create_staff_summary" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "_print_link_staff_summary" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "configure_payroll" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "create_staff" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_employees_simple_dev" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_calendars" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_employees" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_leave_types" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_pay_runs" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "configure_payroll" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "link_staff" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "create_staff" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_payroll_rates" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_tenants" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_tenants" in typed context [no-untyped-call] -apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_users" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Call to untyped function "link_staff" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value -apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "get_employees_simple_dev" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value apps/workflow/management/commands/xero.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "_print_link_staff_summary" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/xero.py:0: error: Call to untyped function "_print_create_staff_summary" in typed context [no-untyped-call] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/timesheet/api/daily_timesheet_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] +apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/management/commands/xero.py:0: note: Use "-> None" if function does not return a value +apps/workflow/middleware.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] +apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/middleware.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/models/service_api_key.py:0: error: Call to untyped function "generate_api_key" of "ServiceAPIKey" in typed context [no-untyped-call] +apps/workflow/models/service_api_key.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/search_telemetry_serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | None") [assignment] +apps/workflow/search_telemetry_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/search_telemetry_serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/serializers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "BooleanField", base class "Field" defined the type as "bool") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "BooleanField", base class "Field" defined the type as "bool") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "CharField", base class "Field" defined the type as "str | _StrPromise | None") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "JSONField", base class "Field" defined the type as "Mapping[str, Any]") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "JSONField", base class "Serializer" defined the type as "ReturnDict[Any, Any]") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "ListField", base class "Field" defined the type as "dict[str, str | _StrPromise]") [assignment] +apps/workflow/serializers.py:0: error: Incompatible types in assignment (expression has type "SettingsFieldSerializer", base class "Serializer" defined the type as "BindingDict") [assignment] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/serializers.py:0: error: Returning Any from function declared to return "str | None" [no-any-return] +apps/workflow/services/db_scrubber.py:0: error: Argument 1 to "add" of "set" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/services/dev_demo_export_scrubber.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/services/error_persistence.py:0: error: Call to untyped function "_extract_caller_context" in typed context [no-untyped-call] +apps/workflow/services/error_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/error_persistence.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/error_persistence.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/services/error_persistence.py:0: error: Incompatible type for "reference_id" of "XeroError" (got "str | None", expected "str | int | Combinable") [misc] +apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_back" [union-attr] +apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_code" [union-attr] +apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | Any | None" has no attribute "f_code" [union-attr] +apps/workflow/services/error_persistence.py:0: error: Item "None" of "FrameType | None" has no attribute "f_back" [union-attr] +apps/workflow/services/llm_service.py:0: error: Call to untyped function "get_default" of "AIProvider" in typed context [no-untyped-call] +apps/workflow/services/llm_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/llm_service.py:0: error: Module "litellm" does not explicitly export attribute "set_verbose" [attr-defined] +apps/workflow/services/llm_service.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload] +apps/workflow/services/llm_service.py:0: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return] +apps/workflow/services/llm_service.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/workflow/services/llm_service.py:0: note: Possible overload variants: +apps/workflow/services/llm_service.py:0: note: def [_T] get(self, AIProviderTypes, _T, /) -> str | _T +apps/workflow/services/llm_service.py:0: note: def get(self, AIProviderTypes, None = ..., /) -> str | None +apps/workflow/services/llm_service.py:0: note: def get(self, AIProviderTypes, str, /) -> str +apps/workflow/services/request.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/workflow/services/request.py:0: error: Returning Any from function declared to return "str" [no-any-return] +apps/workflow/services/search_telemetry.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/search_telemetry.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/session_replay_service.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] +apps/workflow/services/session_replay_service.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/validation.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/validation.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/services/validation.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/services/xero_sync_service.py:0: error: "Callable[[str], None]" has no attribute "delay" [attr-defined] +apps/workflow/services/xero_sync_service.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/services/xero_sync_service.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tasks.py:0: error: Call to untyped function "sync_single_contact" in typed context [no-untyped-call] +apps/workflow/tasks.py:0: error: Call to untyped function "sync_single_invoice" in typed context [no-untyped-call] +apps/workflow/tests/fixtures/xero_responses.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/tests/test_api_schema_coverage.py:0: error: Call to untyped function "_get_all_url_patterns" in typed context [no-untyped-call] +apps/workflow/tests/test_api_schema_coverage.py:0: error: Call to untyped function "_get_all_url_patterns" in typed context [no-untyped-call] +apps/workflow/tests/test_api_schema_coverage.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_app_error_grouped_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_celery_setup.py:0: error: "Callable[[], str]" has no attribute "apply" [attr-defined] +apps/workflow/tests/test_celery_setup.py:0: error: "Celery" has no attribute "main" [attr-defined] +apps/workflow/tests/test_celery_setup.py:0: error: "Celery" has no attribute "tasks" [attr-defined] +apps/workflow/tests/test_celery_setup.py:0: error: "Signal" has no attribute "send" [attr-defined] +apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "assign_staff_to_job" of "JobStaffService" in typed context [no-untyped-call] +apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/tests/test_data_versions_view.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_data_versions_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_dev_demo_export.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_error_grouping.py:0: error: Function is missing a type annotation [no-untyped-def] apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_aged_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_aged_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_aged_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_manager" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_manager" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_manager" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_manager" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_manager" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_localdate_regression.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_client" in typed context [no-untyped-call] -apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] apps/workflow/tests/test_localdate_regression.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Module "apps.purchasing.services.purchasing_rest_service" does not explicitly export attribute "PreconditionFailedError" [attr-defined] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "get_stock_holding_job" of "Stock" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] -apps/purchasing/views/purchasing_rest_views.py:0: error: "PurchaseOrder" has no attribute "detail_lines" [attr-defined] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_none_match" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_match" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_precondition_required_response" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_get_if_match" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "_precondition_required_response" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Dict entry 1 has incompatible type "str": "str"; expected "str": "bool" [dict-item] -apps/purchasing/views/purchasing_rest_views.py:0: error: Dict entry 1 has incompatible type "str": "str"; expected "str": "bool" [dict-item] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Need type annotation for "allocations_by_line" (hint: "allocations_by_line: dict[, ] = ...") [var-annotated] -apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "PurchaseOrderLine | None" has no attribute "id" [union-attr] -apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] -apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] -apps/purchasing/views/purchasing_rest_views.py:0: error: Item "None" of "Job | None" has no attribute "name" [union-attr] -apps/purchasing/views/purchasing_rest_views.py:0: error: Generator has incompatible item type "int"; expected "bool" [misc] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "update_xero_status" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "update_xero_status" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Call to untyped function "create_purchase_order_pdf" in typed context [no-untyped-call] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/purchasing/views/purchasing_rest_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "staff" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_after" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_after" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Value of type "Any | None" is not indexable [index] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_before" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Argument 2 to "assertIn" of "TestCase" has incompatible type "Any | None"; expected "Iterable[Any] | Container[Any]" [arg-type] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "schema_version" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "change_id" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_meta" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "delta_checksum" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "event_type" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_event" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "detail" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/job/tests/test_job_event_tracking.py:0: note: Use "-> None" if function does not return a value -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "_make_job" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "event_type" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "detail" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Item "None" of "JobEvent | None" has no attribute "description" [union-attr] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "untracked_update" in typed context [no-untyped-call] -apps/job/tests/test_job_event_tracking.py:0: error: Call to untyped function "update" in typed context [no-untyped-call] -apps/job/views/quote_sync_views.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] -apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] -apps/job/views/quote_sync_views.py:0: error: Call to untyped function "draft_line_to_dict" in typed context [no-untyped-call] -apps/job/views/quote_sync_views.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item] -apps/job/views/quote_sync_views.py:0: error: Incompatible types in assignment (expression has type "ApplyQuoteErrorResponseSerializer", variable has type "QuoteSyncErrorResponseSerializer") [assignment] -apps/job/views/quote_sync_views.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item] -apps/job/views/quote_sync_views.py:0: error: Incompatible types in assignment (expression has type "ApplyQuoteErrorResponseSerializer", variable has type "QuoteSyncErrorResponseSerializer") [assignment] -apps/workflow/xero_webhooks.py:0: error: "Callable[[str, dict[str, Any]], None]" has no attribute "delay" [attr-defined] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_handle_rate_limit" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_run_handle_rate_limit" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_run_handle_rate_limit" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_run_handle_rate_limit" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "bulk_create_contacts_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "create_company_contact_in_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Call to untyped function "sync_company_to_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_push_contacts.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_search_telemetry.py:0: error: Call to untyped function "result_ids" in typed context [no-untyped-call] +apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_search_telemetry.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_seed_xero_from_database.py:0: error: Call to untyped function "clear_production_xero_ids" in typed context [no-untyped-call] +apps/workflow/tests/test_seed_xero_from_database.py:0: error: Incompatible types in assignment (expression has type "StringIO", variable has type "OutputWrapper") [assignment] +apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] +apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] +apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] +apps/workflow/tests/test_session_replay_api.py:0: error: Argument 1 to "Path" has incompatible type "str | None"; expected "str | PathLike[str]" [arg-type] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_session_replay_api.py:0: error: Value of type "Any | None" is not indexable [index] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "_make_xero_contact" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Call to untyped function "sync_companies" in typed context [no-untyped-call] +apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_sync_clients.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_tasks.py:0: error: Module "apps.workflow.tasks" does not explicitly export attribute "xero_sync_task" [attr-defined] +apps/workflow/tests/test_xero_app_active.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Call to untyped function "_FakeProvider" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Call to untyped function "_FakeProvider" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Cannot assign to a method [method-assign] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "_FakeProvider", variable has type "AccountingProvider") [assignment] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "_FakeProvider", variable has type "AccountingProvider") [assignment] +apps/workflow/tests/test_xero_document_raw_json.py:0: error: Incompatible types in assignment (expression has type "def _attach_workshop_pdf(self, external_id: str) -> None", variable has type "def _attach_workshop_pdf(self, invoice_external_id: str) -> str | None") [assignment] +apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_leave.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Call to untyped function "_draft" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Call to untyped function "_draft" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_payroll_pay_run_flow.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_po_manager.py:0: error: "XeroPurchaseOrderManagerConstructionTests" has no attribute "purchase_order" [attr-defined] +apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "purchase_order" [attr-defined] +apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "supplier" [attr-defined] +apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "supplier" [attr-defined] +apps/workflow/tests/test_xero_po_manager.py:0: error: Call to untyped function "setUpTestData" of "BaseAPITestCase" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_po_manager.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_gen" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_handle_rate_limit" in typed context [no-untyped-call] apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_store_quota_snapshot" in typed context [no-untyped-call] apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_company_floor" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_company_floor" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_consume" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "sync_xero_data" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_quota" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_consume" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "sync_xero_data" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_set_company_floor" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_active_app" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value apps/workflow/tests/test_xero_quota_floor.py:0: error: Module "apps.workflow.api.xero.stock_sync" does not explicitly export attribute "XeroAccount" [attr-defined] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: note: Use "-> None" if function does not return a value apps/workflow/tests/test_xero_quota_floor.py:0: error: Module "apps.workflow.tasks" does not explicitly export attribute "xero_sync_task" [attr-defined] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_quota_floor.py:0: error: Call to untyped function "_gen" in typed context [no-untyped-call] -apps/workflow/tests/test_tasks.py:0: error: Module "apps.workflow.tasks" does not explicitly export attribute "xero_sync_task" [attr-defined] -apps/workflow/tests/test_celery_setup.py:0: error: "Celery" has no attribute "main" [attr-defined] -apps/workflow/tests/test_celery_setup.py:0: error: "Celery" has no attribute "tasks" [attr-defined] -apps/workflow/tests/test_celery_setup.py:0: error: "Callable[[], str]" has no attribute "apply" [attr-defined] -apps/workflow/tests/test_celery_setup.py:0: error: "Signal" has no attribute "send" [attr-defined] -apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_celery_setup.py:0: error: Value of type "Any | None" is not indexable [index] -apps/workflow/tests/test_xero_po_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_po_manager.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_xero_po_manager.py:0: error: Call to untyped function "setUpTestData" of "BaseAPITestCase" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "supplier" [attr-defined] -apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "purchase_order" [attr-defined] -apps/workflow/tests/test_xero_po_manager.py:0: error: "type[XeroPurchaseOrderManagerConstructionTests]" has no attribute "supplier" [attr-defined] -apps/workflow/tests/test_xero_po_manager.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_po_manager.py:0: error: "XeroPurchaseOrderManagerConstructionTests" has no attribute "purchase_order" [attr-defined] -apps/workflow/tests/test_xero_app_active.py:0: error: Value of type "dict[str, Any] | None" is not indexable [index] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "analyze_fields" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "str") [assignment] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run_pipe" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "_run" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Item "None" of "IO[bytes] | None" has no attribute "close" [union-attr] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Need type annotation for "field_samples" [var-annotated] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "cannot_be_pii" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "is_uuid_string" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] -apps/workflow/management/commands/backport_data_backup.py:0: error: Call to untyped function "collect_field_samples" in typed context [no-untyped-call] -apps/workflow/tests/test_seed_xero_from_database.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_seed_xero_from_database.py:0: note: Use "-> None" if function does not return a value -apps/workflow/tests/test_seed_xero_from_database.py:0: error: Incompatible types in assignment (expression has type "StringIO", variable has type "OutputWrapper") [assignment] -apps/workflow/tests/test_seed_xero_from_database.py:0: error: Call to untyped function "clear_production_xero_ids" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_setup_command.py:0: error: Cannot assign to a method [method-assign] apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_earnings_item" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_leave_item" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_earnings_names" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_leave_names" in typed context [no-untyped-call] apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_leave_item" in typed context [no-untyped-call] apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_earnings_item" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_leave_item" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_earnings_names" in typed context [no-untyped-call] -apps/workflow/tests/test_xero_setup_command.py:0: error: Call to untyped function "_leave_names" in typed context [no-untyped-call] apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: error: Cannot assign to a method [method-assign] -apps/workflow/tests/test_xero_setup_command.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/workflow/tests/test_xero_setup_command.py:0: note: Use "-> None" if function does not return a value -apps/workflow/apps.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] -apps/workflow/apps.py:0: error: Function is missing a type annotation [no-untyped-def] -apps/accounts/tests/test_auth_observability.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_auth_observability.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_auth_observability.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] -apps/accounts/tests/test_auth_observability.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_auth_observability.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_auth_observability.py:0: error: Call to untyped function "authenticate" in typed context [no-untyped-call] -apps/accounts/tests/test_auth_observability.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_auth_observability.py:0: note: Use "-> None" if function does not return a value -apps/accounts/tests/test_auth_observability.py:0: error: Function is missing a return type annotation [no-untyped-def] -apps/accounts/tests/test_auth_observability.py:0: note: Use "-> None" if function does not return a value +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "_extract_required_fields_xero" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "resolve_company_from_xero_contact" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "transform_purchase_order" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Call to untyped function "transform_quote" in typed context [no-untyped-call] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/tests/test_xero_transform_contact_resolution.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/utils.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/ai_provider_viewset.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/ai_provider_viewset.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] +apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/app_error_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] +apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] +apps/workflow/views/app_error_view.py:0: error: Missing type arguments for generic type "RetrieveAPIView" [type-arg] +apps/workflow/views/build_id_view.py:0: error: Missing type arguments for generic type "list" [type-arg] +apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/company_defaults_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/company_defaults_logo_api.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/company_defaults_logo_api.py:0: error: Call to untyped function "_remove_if_user_uploaded" in typed context [no-untyped-call] +apps/workflow/views/company_defaults_logo_api.py:0: error: Call to untyped function "_remove_if_user_uploaded" in typed context [no-untyped-call] +apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/company_defaults_logo_api.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/data_versions_view.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Call to untyped function "has_permission" in typed context [no-untyped-call] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/session_replay_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_base_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_base_manager.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/views/xero/xero_base_manager.py:0: note: Use "-> None" if function does not return a value +apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "clean_payload" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_helpers.py:0: error: Call to untyped function "convert_to_pascal_case" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_helpers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_helpers.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument "client_external_id" to "InvoicePayload" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument 1 to "_attach_workshop_pdf" of "XeroInvoiceManager" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Argument 2 to "_add_xero_history_note" of "XeroDocumentManager" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "get_xero_id" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "state_valid_for_xero" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Cannot resolve keyword 'created_at' into field. Choices are: amount_due, billing_metadata, company, company_id, date, django_created_at, django_updated_at, due_date, id, job, job_id, line_items, number, online_url, raw_json, status, tax, total_excl_tax, total_incl_tax, xero_id, xero_last_modified, xero_last_synced, xero_tenant_id [misc] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "paid" [union-attr] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_invoice_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_po_manager.py:0: error: Argument "supplier_external_id" to "POPayload" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "Decimal" [arg-type] +apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "str | None" [arg-type] +apps/workflow/views/xero/xero_po_manager.py:0: error: Argument 1 to "DocumentLineItem" has incompatible type "**dict[str, object]"; expected "str" [arg-type] +apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "save" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_po_manager.py:0: error: Call to untyped function "validate_for_xero_sync" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_po_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_po_manager.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] +apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_po_manager.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_po_manager.py:0: note: Use "-> None" if function does not return a value +apps/workflow/views/xero/xero_quote_manager.py:0: error: Argument "client_external_id" to "QuotePayload" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Argument 2 to "_add_xero_history_note" of "XeroDocumentManager" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "get_xero_id" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "state_valid_for_xero" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Call to untyped function "validate_company" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Incompatible type for "xero_id" of "Quote" (got "str | None", expected "str | UUID") [misc] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "id" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quote" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quoted" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "quoted" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: error: Item "None" of "Job | None" has no attribute "save" [union-attr] +apps/workflow/views/xero/xero_quote_manager.py:0: note: Use "-> None" if function does not return a value +apps/workflow/views/xero/xero_view.py:0: error: Argument "company" to "XeroInvoiceManager" has incompatible type "Company | None"; expected "Company" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "company" to "XeroInvoiceManager" has incompatible type "Company | None"; expected "Company" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroInvoiceManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroInvoiceManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroPurchaseOrderManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "staff" to "XeroPurchaseOrderManager" has incompatible type "Staff | AnonymousUser"; expected "Staff" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument "xero_invoice_id" to "XeroInvoiceManager" has incompatible type "UUID"; expected "str | None" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 1 to "exchange_code_for_token" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "error" has incompatible type "object"; expected "str | _StrPromise" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 2 to "exchange_code_for_token" has incompatible type "str | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Argument 3 to "exchange_code_for_token" has incompatible type "Any | None"; expected "str" [arg-type] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "_get_last_sync_time" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "_get_last_sync_time" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "generate_xero_sync_events" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_active_task_id" of "XeroSyncService" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_active_task_id" of "XeroSyncService" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_messages" of "XeroSyncService" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Call to untyped function "get_messages" of "XeroSyncService" in typed context [no-untyped-call] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible return value type (got "JsonResponse", expected "StreamingHttpResponse") [return-value] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentErrorResponseSerializer", variable has type "XeroDocumentSuccessResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Incompatible types in assignment (expression has type "XeroDocumentSuccessResponseSerializer", variable has type "XeroDocumentErrorResponseSerializer") [assignment] +apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "ListAPIView" [type-arg] +apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "RetrieveAPIView" [type-arg] +apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_view.py:0: error: Missing type arguments for generic type "dict" [type-arg] +apps/workflow/views/xero/xero_view.py:0: error: Returning Any from function declared to return "HttpResponse" [no-any-return] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a return type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Function is missing a type annotation [no-untyped-def] +apps/workflow/views/xero_apps_view.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg] +apps/workflow/views/xero_apps_view.py:0: error: Missing type arguments for generic type "Serializer" [type-arg] +apps/workflow/views/xero_pay_item_viewset.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg] +apps/workflow/xero_webhooks.py:0: error: "Callable[[str, dict[str, Any]], None]" has no attribute "delay" [attr-defined] +docketworks/celery.py:0: error: Module "celery.signals" has no attribute "task_postrun" [attr-defined] +docketworks/celery.py:0: error: Module "celery.signals" has no attribute "task_prerun" [attr-defined] +docketworks/celery.py:0: error: Untyped decorator makes function "_nplusone_setup" untyped [untyped-decorator] +docketworks/celery.py:0: error: Untyped decorator makes function "_nplusone_teardown" untyped [untyped-decorator] +docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "int" has incompatible type "str | None"; expected "str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Argument 1 to "join" has incompatible type "str | None"; expected "str" [arg-type] +docketworks/settings.py:0: error: Call to untyped function "configure_site_for_environment" in typed context [no-untyped-call] +docketworks/settings.py:0: error: Function is missing a return type annotation [no-untyped-def] +docketworks/settings.py:0: error: Function is missing a type annotation [no-untyped-def] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "lower" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "rstrip" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] +docketworks/settings.py:0: error: Item "None" of "str | None" has no attribute "split" [union-attr] +docketworks/settings.py:0: note: Use "-> None" if function does not return a value diff --git a/poetry.lock b/poetry.lock index c5934485d..b93d9df4d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,145 +2,145 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.1" +version = "2.7.1" description = "Happy Eyeballs for asyncio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, - {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, ] [[package]] name = "aiohttp" -version = "3.14.1" +version = "3.14.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "aiohttp-3.14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e"}, - {file = "aiohttp-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491"}, - {file = "aiohttp-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb"}, - {file = "aiohttp-3.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d"}, - {file = "aiohttp-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966"}, - {file = "aiohttp-3.14.1-cp310-cp310-win32.whl", hash = "sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6"}, - {file = "aiohttp-3.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df"}, - {file = "aiohttp-3.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c"}, - {file = "aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2"}, - {file = "aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f"}, - {file = "aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730"}, - {file = "aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85"}, - {file = "aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3"}, - {file = "aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126"}, - {file = "aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5"}, - {file = "aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b"}, - {file = "aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480"}, - {file = "aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d"}, - {file = "aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c"}, - {file = "aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087"}, - {file = "aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3"}, - {file = "aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4"}, - {file = "aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271"}, - {file = "aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847"}, - {file = "aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178"}, - {file = "aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf"}, - {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd"}, - {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe"}, - {file = "aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4"}, - {file = "aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876"}, - {file = "aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da"}, - {file = "aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451"}, - {file = "aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345"}, - {file = "aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588"}, - {file = "aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780"}, - {file = "aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a"}, - {file = "aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8"}, - {file = "aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15"}, - {file = "aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8"}, - {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3"}, - {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba"}, - {file = "aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397"}, - {file = "aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448"}, - {file = "aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004"}, - {file = "aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602"}, - {file = "aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95"}, - {file = "aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444"}, - {file = "aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0"}, - {file = "aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719"}, - {file = "aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec"}, - {file = "aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2"}, - {file = "aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340"}, - {file = "aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3"}, - {file = "aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d"}, - {file = "aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c"}, - {file = "aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365"}, - {file = "aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9"}, - {file = "aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6"}, - {file = "aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32"}, + {file = "aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7"}, + {file = "aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19"}, + {file = "aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71"}, + {file = "aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf"}, + {file = "aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7"}, + {file = "aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc"}, ] [package.dependencies] @@ -188,41 +188,28 @@ files = [ [package.dependencies] vine = ">=5.0.0,<6.0.0" -[[package]] -name = "annotated-doc" -version = "0.0.4" -description = "Document parameters, class attributes, return types, and variables inline, with Annotated." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, - {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, -] - [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.0" description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, + {file = "annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"}, + {file = "annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7"}, ] [[package]] name = "anthropic" -version = "0.112.0" +version = "0.120.0" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anthropic-0.112.0-py3-none-any.whl", hash = "sha256:bcc6268612c716dbb77133dd60fc41d26016d1b81dee9a52314d210193638751"}, - {file = "anthropic-0.112.0.tar.gz", hash = "sha256:e180cd91aa5b9b32e4007fe69892ab128d8a86b9f90825103b1903fbc977d0af"}, + {file = "anthropic-0.120.0-py3-none-any.whl", hash = "sha256:591bd531563ec7b63a1e138f5c11f14cb94edda99623b349c2ce2ece8e08b8a5"}, + {file = "anthropic-0.120.0.tar.gz", hash = "sha256:6ba6007dc9b00365b20f6101a6618f5196ac1ceef81512e4b5cc0e7436d4975d"}, ] [package.dependencies] @@ -236,23 +223,24 @@ sniffio = "*" typing-extensions = ">=4.14,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] +aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9,<1)"] aws = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +google-cloud = ["google-auth[requests] (>=2,<3)"] mcp = ["mcp (>=1.0) ; python_version >= \"3.10\""] vertex = ["google-auth[requests] (>=2,<3)"] webhooks = ["standardwebhooks (>=1.0.1,<2)"] [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.2" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, + {file = "anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494"}, + {file = "anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"}, ] [package.dependencies] @@ -264,58 +252,62 @@ trio = ["trio (>=0.32.0)"] [[package]] name = "asgiref" -version = "3.11.1" +version = "3.12.1" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"}, - {file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"}, + {file = "asgiref-3.12.1-py3-none-any.whl", hash = "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094"}, + {file = "asgiref-3.12.1.tar.gz", hash = "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340"}, ] [package.extras] -tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] +mypy = ["mypy (>=1.14.0)"] +tests = ["pytest", "pytest-asyncio"] [[package]] name = "ast-serialize" -version = "0.4.0" +version = "0.6.0" description = "Python bindings for mypy AST serialization" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ast_serialize-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a6f26937ce0293aafbece0e39019e020369a5a70486ff4088227f0cc888844a9"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:074032142777e3e6091977dc3c5146a8ca58ae6825b7f64e9a0b604153ddabd8"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:404f3462b4532e13a70b8849bba241dbd82e30043ff58d98c7e762fd925b116a"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:97c55336e16f5c4ca2bde7be94cca4b8f7d665d64f7008925a82e02707ba14ac"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:732b4ef76adcb0f298a7d18c4558336d83b1384f9ae0c7eaa1dc8d031b0a4390"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3db87c4772097c0782250bcd550d66b1189a8c889793c7bcf153f4fee70005c"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43729a5e369ebbe7750635c0c206bc616fcd36e703cb9c4497d6b4df0291ee64"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:91d3786f3929786cdc4eeedfd110abb4603e7f6c1390c5af398f333a947b742d"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7fba7315fd4bd87cb5560792709f6e66e0606402d362c0a38dd32dfb66ba6066"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4db9769d57deb5545ce56ebbbbe3436dcc0ae2688ce14c295cd14e106624ece7"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:dcd04f85a29deb80400e8987cfaceb9907140f763453cbffdbd6ff36f1b32c12"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:905fc11940831454d93589bd7ce2acb6a5eb01c2936156f751d2a21087c98cd3"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:3bdde2c4570143791f636aed4e3ef868f5b46eb90a18f8d5c41dd045aab08bef"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6551d55b8607b97a7755683d743200b398c61a0b71a11b7f00c89c335a11d0f4"}, - {file = "ast_serialize-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7234ff086cb152ea2a3b7ef895b5ebeb6d80779df049d5c6431c8e3536d5b03c"}, - {file = "ast_serialize-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcded5056d9f3d201df7833082c07ebcbc566ffc3d4105c9fc9fe278fa086ecb"}, - {file = "ast_serialize-0.4.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bd50d201098aae0d202805fe9606c0545492f69a3ec4403337e32c54ad29fc41"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6615b39cd747967c3aabe68bf3f5f26748e823cc6b474ddc1510ed188a824149"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91362c0a9fdf1c344b7f50a5b0508b11a0732102998fbd754a191f7187e77031"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70d9c5d527bbfa69bd3c7d17dac11fb6781e36186a434a06d7d5892e0b2f88f9"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4738790cf54d8b416de992b87ee567056980bc82134d52458bd4985f389d1658"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:faa008dccfcb793ae9101325e4d6d026caaa5d845c2182f03749c759834b0a3a"}, - {file = "ast_serialize-0.4.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c5245228e65d38cb48e1251f0ca71b0fa417e527141491e8c92f740e8e2d121"}, - {file = "ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8f5153e9c44a02e61f4042c5f9249d2e8a759773d621a0b2f445a899e536e181"}, - {file = "ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1e1fb90def261f6a0db885876f7e1a49ad2dbac38ad9f2f62dba2f9543af16e7"}, - {file = "ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf2ff7b654c8e95143e20f5d75878cbb78b65b928b26c4d58ef71cdba9d6d981"}, - {file = "ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:90fc5c0d35a22f1a92dd33635508626d50f8fc64deb897c23e78e666a60804c9"}, - {file = "ast_serialize-0.4.0-cp39-abi3-win32.whl", hash = "sha256:9ecd6a1fc1b86f1f4e8ae206759b6319c10019706b3496b01b54d02b9b2cd918"}, - {file = "ast_serialize-0.4.0-cp39-abi3-win_amd64.whl", hash = "sha256:79c8d015c771c8bfdb1208003b227b27c40034790a2c29c09f2317a041825ce2"}, - {file = "ast_serialize-0.4.0-cp39-abi3-win_arm64.whl", hash = "sha256:1026f565a7ab846337c630909089b3346a2fe417bf1552b1581ab01852137407"}, - {file = "ast_serialize-0.4.0.tar.gz", hash = "sha256:74e4e634ab82d1466acf0be27043178570b98ebeaa3165f9240a6fad4c286471"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24"}, + {file = "ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14"}, + {file = "ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596"}, + {file = "ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b"}, + {file = "ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b"}, + {file = "ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32"}, + {file = "ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b"}, + {file = "ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a"}, + {file = "ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e"}, + {file = "ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b"}, + {file = "ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1"}, + {file = "ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e"}, + {file = "ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e"}, + {file = "ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe"}, ] [[package]] @@ -374,14 +366,14 @@ pycodestyle = ">=2.12.0" [[package]] name = "beautifulsoup4" -version = "4.14.3" +version = "4.15.0" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, - {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, + {file = "beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9"}, + {file = "beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7"}, ] [package.dependencies] @@ -472,14 +464,14 @@ files = [ [[package]] name = "cachetools" -version = "7.1.4" +version = "7.1.6" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54"}, - {file = "cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6"}, + {file = "cachetools-7.1.6-py3-none-any.whl", hash = "sha256:2c12e255780330af28b91bb7fb96cce4c766f04e38396b9a24510190a5827096"}, + {file = "cachetools-7.1.6.tar.gz", hash = "sha256:c7a79e7f30ba9943c1cefd08cc36f006aaae086e017af9166f1d59d6170c47e1"}, ] [[package]] @@ -542,108 +534,124 @@ zstd = ["zstandard (==0.23.0)"] [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db"}, - {file = "certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432"}, + {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, + {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, ] [[package]] name = "cffi" -version = "2.0.0" +version = "2.1.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, + {file = "cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0"}, + {file = "cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd"}, + {file = "cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46"}, + {file = "cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2"}, + {file = "cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd"}, + {file = "cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3"}, + {file = "cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0"}, + {file = "cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43"}, + {file = "cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c"}, + {file = "cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd"}, + {file = "cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f"}, + {file = "cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da"}, + {file = "cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc"}, + {file = "cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7"}, + {file = "cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93"}, + {file = "cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2"}, + {file = "cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c"}, + {file = "cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f"}, + {file = "cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565"}, + {file = "cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c"}, + {file = "cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02"}, + {file = "cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e"}, + {file = "cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479"}, + {file = "cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458"}, + {file = "cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d"}, + {file = "cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f"}, + {file = "cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde"}, + {file = "cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d"}, + {file = "cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7"}, + {file = "cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b"}, + {file = "cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7"}, + {file = "cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66"}, + {file = "cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe"}, + {file = "cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b"}, + {file = "cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a"}, + {file = "cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384"}, + {file = "cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6"}, + {file = "cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda"}, + {file = "cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b"}, + {file = "cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a"}, + {file = "cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea"}, + {file = "cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db"}, + {file = "cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f"}, + {file = "cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d"}, + {file = "cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0"}, + {file = "cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224"}, + {file = "cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c"}, + {file = "cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a"}, + {file = "cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2"}, + {file = "cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512"}, + {file = "cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f"}, + {file = "cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a"}, + {file = "cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3"}, + {file = "cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d"}, + {file = "cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac"}, + {file = "cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6"}, + {file = "cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913"}, + {file = "cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d"}, + {file = "cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5"}, + {file = "cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce"}, + {file = "cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326"}, + {file = "cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd"}, + {file = "cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb"}, + {file = "cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804"}, + {file = "cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714"}, + {file = "cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376"}, + {file = "cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98"}, + {file = "cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13"}, + {file = "cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d"}, + {file = "cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056"}, + {file = "cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4"}, + {file = "cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94"}, + {file = "cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76"}, + {file = "cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5"}, + {file = "cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8"}, + {file = "cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c"}, + {file = "cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001"}, + {file = "cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3"}, + {file = "cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc"}, + {file = "cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699"}, + {file = "cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022"}, + {file = "cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0"}, + {file = "cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1"}, + {file = "cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28"}, + {file = "cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629"}, + {file = "cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6"}, + {file = "cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853"}, + {file = "cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda"}, + {file = "cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc"}, + {file = "cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca"}, + {file = "cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d"}, + {file = "cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8"}, + {file = "cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd"}, + {file = "cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f"}, + {file = "cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc"}, + {file = "cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9"}, + {file = "cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b"}, + {file = "cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5"}, + {file = "cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210"}, + {file = "cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9"}, ] markers = {main = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\"", dev = "platform_python_implementation != \"PyPy\""} @@ -707,153 +715,117 @@ tests = ["async-timeout", "cryptography (>=1.3.0)", "pytest", "pytest-asyncio", [[package]] name = "charset-normalizer" -version = "3.4.7" +version = "3.4.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe"}, + {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, + {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, ] [[package]] name = "click" -version = "8.3.3" +version = "8.4.2" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, - {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, + {file = "click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"}, + {file = "click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6"}, ] [package.dependencies] @@ -1129,42 +1101,42 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "debugpy" -version = "1.8.20" +version = "1.8.21" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64"}, - {file = "debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642"}, - {file = "debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2"}, - {file = "debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893"}, - {file = "debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b"}, - {file = "debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344"}, - {file = "debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec"}, - {file = "debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb"}, - {file = "debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d"}, - {file = "debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b"}, - {file = "debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390"}, - {file = "debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3"}, - {file = "debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a"}, - {file = "debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf"}, - {file = "debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393"}, - {file = "debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7"}, - {file = "debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173"}, - {file = "debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad"}, - {file = "debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f"}, - {file = "debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be"}, - {file = "debugpy-1.8.20-cp38-cp38-macosx_15_0_x86_64.whl", hash = "sha256:b773eb026a043e4d9c76265742bc846f2f347da7e27edf7fe97716ea19d6bfc5"}, - {file = "debugpy-1.8.20-cp38-cp38-manylinux_2_34_x86_64.whl", hash = "sha256:20d6e64ea177ab6732bffd3ce8fc6fb8879c60484ce14c3b3fe183b1761459ca"}, - {file = "debugpy-1.8.20-cp38-cp38-win32.whl", hash = "sha256:0dfd9adb4b3c7005e9c33df430bcdd4e4ebba70be533e0066e3a34d210041b66"}, - {file = "debugpy-1.8.20-cp38-cp38-win_amd64.whl", hash = "sha256:60f89411a6c6afb89f18e72e9091c3dfbcfe3edc1066b2043a1f80a3bbb3e11f"}, - {file = "debugpy-1.8.20-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:bff8990f040dacb4c314864da95f7168c5a58a30a66e0eea0fb85e2586a92cd6"}, - {file = "debugpy-1.8.20-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:70ad9ae09b98ac307b82c16c151d27ee9d68ae007a2e7843ba621b5ce65333b5"}, - {file = "debugpy-1.8.20-cp39-cp39-win32.whl", hash = "sha256:9eeed9f953f9a23850c85d440bf51e3c56ed5d25f8560eeb29add815bd32f7ee"}, - {file = "debugpy-1.8.20-cp39-cp39-win_amd64.whl", hash = "sha256:760813b4fff517c75bfe7923033c107104e76acfef7bda011ffea8736e9a66f8"}, - {file = "debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7"}, - {file = "debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33"}, + {file = "debugpy-1.8.21-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:8eeab7b5462f683452c57c0126aaa5ec4e974ddb705f39ba87dff8818c8e08f9"}, + {file = "debugpy-1.8.21-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:0fddfdc130ac6d8bfc0415b0409822fa901c8f310e5c945ac5653a0352532344"}, + {file = "debugpy-1.8.21-cp310-cp310-win32.whl", hash = "sha256:72b5d676c4cbfac3bac5bb01c138a4656e843f93f03ce2a5f4e394ad49fbee73"}, + {file = "debugpy-1.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a7fe47fd23da57b9e0bec3f4a8ee65a2dc55782455ed7f2141d75ab5d2eaeef5"}, + {file = "debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264"}, + {file = "debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc"}, + {file = "debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e"}, + {file = "debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7"}, + {file = "debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e"}, + {file = "debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176"}, + {file = "debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9"}, + {file = "debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c"}, + {file = "debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88"}, + {file = "debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2"}, + {file = "debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1"}, + {file = "debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0"}, + {file = "debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782"}, + {file = "debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e"}, + {file = "debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c"}, + {file = "debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8"}, + {file = "debugpy-1.8.21-cp38-cp38-macosx_15_0_x86_64.whl", hash = "sha256:0042da0ecd0a8b50dc4a54395ecd870d258d73fa18776f50c91fdcabdcad2675"}, + {file = "debugpy-1.8.21-cp38-cp38-manylinux_2_34_x86_64.whl", hash = "sha256:ffd932c6796afadab6993ec96745918a8cb2444dbd392074f769db5ea40ab440"}, + {file = "debugpy-1.8.21-cp38-cp38-win32.whl", hash = "sha256:4e7c2d784d78ad4b71a5f8cd7b59c167719ec8a7a0211dbb3eb1bfeda78bc4e2"}, + {file = "debugpy-1.8.21-cp38-cp38-win_amd64.whl", hash = "sha256:aa9d941d6dfe3d0407e4b3ca0b9ec466030e260fbf1174094f68785680f66db6"}, + {file = "debugpy-1.8.21-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:9f5171176a0084b95d2ebe55a4d1f7b2a75b74c5dbec577ebd3a85c740551c36"}, + {file = "debugpy-1.8.21-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:f15c10084f9861b5e8414a48f18f8e4aadf51a98a59e72c16aa28281ca994672"}, + {file = "debugpy-1.8.21-cp39-cp39-win32.whl", hash = "sha256:4e70cc8b5079f885cb43910924ee0aab73b8b6b2a14eff23afdd9895d86e79eb"}, + {file = "debugpy-1.8.21-cp39-cp39-win_amd64.whl", hash = "sha256:e935f9dc0501be523c8a8e1853c39432e1354e9ece717ae5998fd2371c4542c3"}, + {file = "debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92"}, + {file = "debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6"}, ] [[package]] @@ -1218,14 +1190,14 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.3" description = "Distribution utilities" optional = false python-versions = "*" groups = ["main", "dev"] files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, + {file = "distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b"}, + {file = "distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed"}, ] [[package]] @@ -1315,14 +1287,14 @@ Django = ">=2.2" [[package]] name = "django-filter" -version = "25.2" +version = "26.1" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "django_filter-25.2-py3-none-any.whl", hash = "sha256:9c0f8609057309bba611062fe1b720b4a873652541192d232dd28970383633e3"}, - {file = "django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23"}, + {file = "django_filter-26.1-py3-none-any.whl", hash = "sha256:7d98ef2899218e6242619b532cb1b95af14e09dfcf74844aecb550ad27b59ff2"}, + {file = "django_filter-26.1.tar.gz", hash = "sha256:66ea04031b068c77c86e1ac26ced7a3f8f13ce797f5795751707e3deefc58054"}, ] [package.dependencies] @@ -1370,18 +1342,18 @@ uritemplate = ">=4.1.1,<5.0.0" [[package]] name = "django-simple-history" -version = "3.11.0" +version = "3.13.0" description = "Store model history and view/revert changes from admin site." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "django_simple_history-3.11.0-py3-none-any.whl", hash = "sha256:f3c298db49e418ffce7fb709a5e83108452ea2179ec5c4b9232484c25427192a"}, - {file = "django_simple_history-3.11.0.tar.gz", hash = "sha256:2c587479cf2c3071e9aa555d0d11b73676994db4910770958f57659ade2deffe"}, + {file = "django_simple_history-3.13.0-py3-none-any.whl", hash = "sha256:1cf1d83054479f64aee022023afc1671843aa0a99888799c5c4c3966b0252521"}, + {file = "django_simple_history-3.13.0.tar.gz", hash = "sha256:ca3c1e4ccd5f16f45fbbef6015ee2a246465fd9bd924100df8e9dfb368c4f26c"}, ] [package.dependencies] -django = ">=4.2" +django = ">=5.2" [[package]] name = "django-solo" @@ -1400,14 +1372,14 @@ django = ">=4.2" [[package]] name = "django-stubs" -version = "6.0.3" +version = "6.0.7" description = "Mypy stubs for Django" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "django_stubs-6.0.3-py3-none-any.whl", hash = "sha256:5fee22bcbbad59a78c727a820b6f4e68ff442ca76a922b7002e57c25dd7cb390"}, - {file = "django_stubs-6.0.3.tar.gz", hash = "sha256:ee895f403c373608eeb50822f0733f9d9ec5ab12731d4ab58956053bb95fdd9e"}, + {file = "django_stubs-6.0.7-py3-none-any.whl", hash = "sha256:7ed9a14c438e589272ca04e966dee82a4d1ff7ca5c2171bc986c50a0d03ec35b"}, + {file = "django_stubs-6.0.7.tar.gz", hash = "sha256:bc55431c0af745a64e39cf33a8d36c87dccbedeae2fe26fab47dd355270e8538"}, ] [package.dependencies] @@ -1417,20 +1389,20 @@ types-pyyaml = "*" typing-extensions = ">=4.11.0" [package.extras] -compatible-mypy = ["mypy (>=1.13,<1.21)"] +compatible-mypy = ["mypy (>=1.13,<2.4)"] oracle = ["oracledb"] redis = ["redis", "types-redis"] [[package]] name = "django-stubs-ext" -version = "6.0.3" +version = "6.0.7" description = "Monkey-patching and extensions for django-stubs" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "django_stubs_ext-6.0.3-py3-none-any.whl", hash = "sha256:9e4105955419ae310d7da9cfd808e039d4dae3092c628f021057bb4f2c237f8f"}, - {file = "django_stubs_ext-6.0.3.tar.gz", hash = "sha256:3307d42132bc295d5744de6276bc5fdf6896efc70f891e21c0ae8bdf529d2762"}, + {file = "django_stubs_ext-6.0.7-py3-none-any.whl", hash = "sha256:53a9c7c5a7c7e718cc6308cfce1e7470f2cac0b9d38dbcd60fbfa82704f1d592"}, + {file = "django_stubs_ext-6.0.7.tar.gz", hash = "sha256:c3172c5126614fd2a44d0196b313b44c21f717cb09477ba52b447d41f4ce613e"}, ] [package.dependencies] @@ -1439,18 +1411,18 @@ typing-extensions = "*" [[package]] name = "django-timezone-field" -version = "7.2.1" +version = "7.2.2" description = "A Django app providing DB, form, and REST framework fields for zoneinfo and pytz timezone objects." optional = false python-versions = "<4.0,>=3.8" groups = ["main"] files = [ - {file = "django_timezone_field-7.2.1-py3-none-any.whl", hash = "sha256:276915b72c5816f57c3baf9e43f816c695ef940d1b21f91ebf6203c09bf4ad44"}, - {file = "django_timezone_field-7.2.1.tar.gz", hash = "sha256:def846f9e7200b7b8f2a28fcce2b78fb2d470f6a9f272b07c4e014f6ba4c6d2e"}, + {file = "django_timezone_field-7.2.2-py3-none-any.whl", hash = "sha256:30354d0f37462a0b9b5c289e271580a6be9b58dea30e7bf88435372882c8fa7a"}, + {file = "django_timezone_field-7.2.2.tar.gz", hash = "sha256:a004d0b19fe10bf5964cb21a65b36324b16a61879e4711c0dafdf8d6253e8ebc"}, ] [package.dependencies] -Django = ">=3.2,<6.1" +Django = ">=4.2,<6.2" [[package]] name = "djangorestframework" @@ -1494,23 +1466,23 @@ test = ["cryptography", "freezegun", "pytest", "pytest-cov", "pytest-django", "p [[package]] name = "djangorestframework-stubs" -version = "3.16.9" +version = "3.17.0" description = "PEP-484 stubs for django-rest-framework" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "djangorestframework_stubs-3.16.9-py3-none-any.whl", hash = "sha256:27b3e245d5f9c22ff6988d9e54388249f98f88608cc2b365b71e9f39dd096958"}, - {file = "djangorestframework_stubs-3.16.9.tar.gz", hash = "sha256:b1abb97490c90c85eabcd09b8ecbadae1b9360f21ad3021abf830227c0129697"}, + {file = "djangorestframework_stubs-3.17.0-py3-none-any.whl", hash = "sha256:babe2703f0401507780848439f49f76222a178b4fc73a6dcb30d0952a0a6dbc6"}, + {file = "djangorestframework_stubs-3.17.0.tar.gz", hash = "sha256:74c0307cad304c03c15aee5955ec2fdeab0ac854b5bea569dc124892792e2b96"}, ] [package.dependencies] -django-stubs = ">=5.2.9" +django-stubs = ">=6.0.4" types-pyyaml = "*" typing-extensions = ">=4.0" [package.extras] -compatible-mypy = ["django-stubs[compatible-mypy]", "mypy (>=1.13,<1.21)"] +compatible-mypy = ["django-stubs[compatible-mypy]", "mypy (>=1.13,<2.2)"] coreapi = ["coreapi (>=2.0.0)"] markdown = ["types-markdown (>=0.1.5)"] requests = ["types-requests"] @@ -1534,14 +1506,14 @@ test = ["pytest"] [[package]] name = "drf-spectacular" -version = "0.29.0" +version = "0.30.0" description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "drf_spectacular-0.29.0-py3-none-any.whl", hash = "sha256:d1ee7c9535d89848affb4427347f7c4a22c5d22530b8842ef133d7b72e19b41a"}, - {file = "drf_spectacular-0.29.0.tar.gz", hash = "sha256:0a069339ea390ce7f14a75e8b5af4a0860a46e833fd4af027411a3e94fc1a0cc"}, + {file = "drf_spectacular-0.30.0-py3-none-any.whl", hash = "sha256:006cf5921ebe20a9bd24f7c846261ebbf78780be5961b0d6e87afaa82afd62ff"}, + {file = "drf_spectacular-0.30.0.tar.gz", hash = "sha256:53e79e7ba00e240441b63c32273754a5368e4c2ab44a19f2595277cc1cd559c9"}, ] [package.dependencies] @@ -1558,21 +1530,24 @@ sidecar = ["drf-spectacular-sidecar"] [[package]] name = "dropbox" -version = "12.0.2" +version = "12.2.1" description = "Official Dropbox API Client" optional = false -python-versions = "*" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "dropbox-12.0.2-py2-none-any.whl", hash = "sha256:4b8207a9f4afd33726ec886c0d223f4bbc42fe649b87718690a24704f5e24c0c"}, - {file = "dropbox-12.0.2-py3-none-any.whl", hash = "sha256:c5b7e9c2668adb6b12dcecd84342565dc50f7d35ab6a748d155cb79040979d1c"}, - {file = "dropbox-12.0.2.tar.gz", hash = "sha256:50057fd5ad5fcf047f542dfc6747a896e7ef982f1b5f8500daf51f3abd609962"}, + {file = "dropbox-12.2.1-py3-none-any.whl", hash = "sha256:a0f9cdc3505b104f7a14b35f81a8a0be6f44eb738475ef324243684c25efe1fb"}, + {file = "dropbox-12.2.1.tar.gz", hash = "sha256:61fcb821f7e8585aa4b9e2abc94801aaf8d65ed5ca89313450e56d51c46add7e"}, ] [package.dependencies] requests = ">=2.16.2" -six = ">=1.12.0" -stone = ">=2,<3.3.3" +stone = ">=3.5.3,<4" + +[package.extras] +dev = ["build", "flake8", "twine", "wheel"] +docs = ["sphinx", "sphinx_rtd_theme"] +test = ["coverage", "mock", "pytest", "pytest-mock", "stone (>=3.5.3,<4)"] [[package]] name = "et-xmlfile" @@ -1603,14 +1578,14 @@ tests = ["pytest"] [[package]] name = "faker" -version = "40.15.0" +version = "40.36.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "faker-40.15.0-py3-none-any.whl", hash = "sha256:71ab3c3370da9d2205ab74ffb0fd51273063ad562b3a3bb69d0026a20923e318"}, - {file = "faker-40.15.0.tar.gz", hash = "sha256:20f3a6ec8c266b74d4c554e34118b21c3c2056c0b4a519d15c8decb3a4e6e795"}, + {file = "faker-40.36.0-py3-none-any.whl", hash = "sha256:82b9497d9cfe017048075bcf969298a74b1b6e39f5e4dad1211085d1133f7b62"}, + {file = "faker-40.36.0.tar.gz", hash = "sha256:754048c76c03afa7de83eee8f4bcee3cf668cbb7d995f54a4e9678db7f110308"}, ] [package.dependencies] @@ -1710,14 +1685,14 @@ files = [ [[package]] name = "filelock" -version = "3.29.0" +version = "3.32.0" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, - {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, + {file = "filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3"}, + {file = "filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402"}, ] [[package]] @@ -1756,62 +1731,62 @@ dev = ["Flit (>=3.4)", "pyTest (>=7)", "pyTest-cov (>=7) ; python_version >= \"3 [[package]] name = "fonttools" -version = "4.62.1" +version = "4.63.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c"}, - {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a"}, - {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3"}, - {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23"}, - {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d"}, - {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae"}, - {file = "fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed"}, - {file = "fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9"}, - {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7"}, - {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14"}, - {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7"}, - {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b"}, - {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1"}, - {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416"}, - {file = "fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53"}, - {file = "fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2"}, - {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974"}, - {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9"}, - {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936"}, - {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392"}, - {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04"}, - {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d"}, - {file = "fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c"}, - {file = "fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42"}, - {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79"}, - {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe"}, - {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68"}, - {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1"}, - {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069"}, - {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9"}, - {file = "fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24"}, - {file = "fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056"}, - {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca"}, - {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca"}, - {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782"}, - {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae"}, - {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7"}, - {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a"}, - {file = "fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800"}, - {file = "fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e"}, - {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82"}, - {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260"}, - {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4"}, - {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b"}, - {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87"}, - {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c"}, - {file = "fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a"}, - {file = "fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e"}, - {file = "fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd"}, - {file = "fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d"}, + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b"}, + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69"}, + {file = "fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e"}, + {file = "fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007"}, + {file = "fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb"}, + {file = "fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78"}, + {file = "fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263"}, + {file = "fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27"}, + {file = "fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380"}, + {file = "fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4"}, + {file = "fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616"}, + {file = "fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196"}, + {file = "fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8"}, + {file = "fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419"}, + {file = "fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d"}, + {file = "fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0"}, ] [package.extras] @@ -2024,94 +1999,42 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "backports-zstd ; python_version < \"3.14\"", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas (<3.0.0)", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr (<3.2.0)", "zstandard ; python_version < \"3.14\""] tqdm = ["tqdm"] -[[package]] -name = "google-ai-generativelanguage" -version = "0.6.15" -description = "Google Ai Generativelanguage API client library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c"}, - {file = "google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0.dev0", extras = ["grpc"]} -google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" -proto-plus = [ - {version = ">=1.25.0,<2.0.0.dev0", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0.dev0"}, -] -protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" - [[package]] name = "google-api-core" -version = "2.25.2" +version = "2.33.0" description = "Google API client core library" optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version >= \"3.14\"" -files = [ - {file = "google_api_core-2.25.2-py3-none-any.whl", hash = "sha256:e9a8f62d363dc8424a8497f4c2a47d6bcda6c16514c935629c257ab5d10210e7"}, - {file = "google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.0" -googleapis-common-protos = ">=1.56.2,<2.0.0" -grpcio = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -grpcio-status = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -proto-plus = {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" -requests = ">=2.18.0,<3.0.0" - -[package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] - -[[package]] -name = "google-api-core" -version = "2.30.3" -description = "Google API client core library" -optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] -markers = "python_version < \"3.14\"" files = [ - {file = "google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8"}, - {file = "google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b"}, + {file = "google_api_core-2.33.0-py3-none-any.whl", hash = "sha256:a2e22a0c1d0f03eafff1858b38cf46f832d5902b0c052235bf0ab8402929fbdc"}, + {file = "google_api_core-2.33.0.tar.gz", hash = "sha256:3a36bcc3e319783f4c97da41f6f45ea6ffcaa55848e341de16e09cb70243c2bb"}, ] [package.dependencies] google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.63.2,<2.0.0" -grpcio = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} -grpcio-status = {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} proto-plus = [ {version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.24.0,<2.0.0", markers = "python_version < \"3.13\""}, ] -protobuf = ">=4.25.8,<8.0.0" -requests = ">=2.20.0,<3.0.0" +protobuf = ">=5.29.6,<8.0.0" +requests = ">=2.33.0,<3.0.0" [package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] +async-rest = ["aiohttp (>=3.13.4)", "google-auth[aiohttp] (>=2.14.1,<3.0.0)"] +grpc = ["grpcio (>=1.41.0,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.41.0,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] [[package]] name = "google-api-python-client" -version = "2.195.0" +version = "2.198.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "google_api_python_client-2.195.0-py3-none-any.whl", hash = "sha256:753e62057f23049a89534bea0162b60fe391b85fb86d80bcdf884d05ec91c5bf"}, - {file = "google_api_python_client-2.195.0.tar.gz", hash = "sha256:c72cf2661c3addf01c880ce60541e83e1df354644b874f7f9d8d5ed2070446ae"}, + {file = "google_api_python_client-2.198.0-py3-none-any.whl", hash = "sha256:fabac935474e817da5e662ff61bf7139439d6f92b32d332a7318a2d45931e03e"}, + {file = "google_api_python_client-2.198.0.tar.gz", hash = "sha256:dfe3e16fb241af6e9c460a33f65085b3450e05cea09364f6b5d8997fb7e43e2a"}, ] [package.dependencies] @@ -2123,60 +2046,64 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-api-python-client-stubs" -version = "1.36.0" +version = "1.39.0" description = "Type stubs for google-api-python-client" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "google_api_python_client_stubs-1.36.0-py3-none-any.whl", hash = "sha256:e03fbcabc3d25cdb276de0aa4677012f544b1eaabd8b48f6dfd08095976c6a05"}, - {file = "google_api_python_client_stubs-1.36.0.tar.gz", hash = "sha256:abbe9584564993f754c7367258239f6ba7080847288d91d9cad75ffccce19fa3"}, + {file = "google_api_python_client_stubs-1.39.0-py3-none-any.whl", hash = "sha256:eedef91ce51cf7a83ac861608dbfee5b995f9d7e89bbe4d1dad334cb8b6a8326"}, + {file = "google_api_python_client_stubs-1.39.0.tar.gz", hash = "sha256:c5af3e779011cc3447e0ab89c72159351da7495692201d08a0d23e6b7b67c097"}, ] [package.dependencies] -google-api-python-client = ">=2.195.0" +google-api-python-client = ">=2.198.0" types-httplib2 = ">=0.22.0.2" typing-extensions = ">=3.10.0" [[package]] name = "google-auth" -version = "2.53.0" +version = "2.56.2" description = "Google Authentication Library" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "google_auth-2.53.0-py3-none-any.whl", hash = "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68"}, - {file = "google_auth-2.53.0.tar.gz", hash = "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c"}, + {file = "google_auth-2.56.2-py3-none-any.whl", hash = "sha256:c8270ea95b2697b74e3d8438ae9c5b898e38b623b915c7b5c5635921e7de68a6"}, + {file = "google_auth-2.56.2.tar.gz", hash = "sha256:e28f103ca8091fb7012b99c44243d7366c29863713b8e34a220c3322b7a07051"}, ] [package.dependencies] -cryptography = ">=38.0.3" +cryptography = [ + {version = ">=38.0.3", markers = "python_version < \"3.14\""}, + {version = ">=41.0.5", markers = "python_version >= \"3.14\""}, +] pyasn1-modules = ">=0.2.1" -requests = {version = ">=2.20.0,<3.0.0", optional = true, markers = "extra == \"requests\""} +requests = {version = ">=2.30.0,<3.0.0", optional = true, markers = "extra == \"requests\""} [package.extras] -aiohttp = ["aiohttp (>=3.8.0,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] -cryptography = ["cryptography (>=38.0.3)"] -enterprise-cert = ["pyopenssl"] +aiohttp = ["aiohttp (>=3.8.0,<4.0.0) ; python_version < \"3.14\"", "aiohttp (>=3.9.0,<4.0.0) ; python_version >= \"3.14\"", "requests (>=2.30.0,<3.0.0)"] +cryptography = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] +enterprise-cert = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] +grpc = ["grpcio (>=1.59.0,<2.0.0) ; python_version < \"3.14\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] pyjwt = ["pyjwt (>=2.0)"] -pyopenssl = ["pyopenssl (>=20.0.0)"] +pyopenssl = ["cryptography (>=38.0.3) ; python_version < \"3.14\"", "cryptography (>=41.0.5) ; python_version >= \"3.14\""] reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0)"] -rsa = ["rsa (>=3.1.4,<5)"] -testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.8.0,<4.0.0)", "aioresponses", "flask", "freezegun", "grpcio", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] -urllib3 = ["packaging", "urllib3"] +requests = ["requests (>=2.30.0,<3.0.0)"] +rsa = ["rsa (>=4.0.0,<5)"] +testing = ["aiohttp (>=3.8.0,<4.0.0) ; python_version < \"3.14\"", "aiohttp (>=3.9.0,<4.0.0) ; python_version >= \"3.14\"", "aioresponses", "flask", "freezegun", "grpcio (>=1.59.0,<2.0.0) ; python_version < \"3.14\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "packaging (>=20.0)", "pyjwt (>=2.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.30.0,<3.0.0)", "responses", "urllib3 (>=1.26.15,<3.0.0)"] +urllib3 = ["packaging (>=20.0)", "urllib3 (>=1.26.15,<3.0.0)"] [[package]] name = "google-auth-httplib2" -version = "0.3.1" +version = "0.4.0" description = "Google Authentication Library: httplib2 transport" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "google_auth_httplib2-0.3.1-py3-none-any.whl", hash = "sha256:682356a90ef4ba3d06548c37e9112eea6fc00395a11b0303a644c1a86abc275c"}, - {file = "google_auth_httplib2-0.3.1.tar.gz", hash = "sha256:0af542e815784cb64159b4469aa5d71dd41069ba93effa006e1916b1dcd88e55"}, + {file = "google_auth_httplib2-0.4.0-py3-none-any.whl", hash = "sha256:8e55cfafa3358cba85f6cad4a886138e88e158d71e7e5c9ee5936a5c1507fb91"}, + {file = "google_auth_httplib2-0.4.0.tar.gz", hash = "sha256:d5b030a204b7a4b4d553ba9ca701b62481ee2b74419325580be70f7d85ffed35"}, ] [package.dependencies] @@ -2202,20 +2129,20 @@ types-requests = ">=2.25.9,<3.0.0" [[package]] name = "google-genai" -version = "2.10.0" +version = "2.14.0" description = "GenAI Python SDK" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "google_genai-2.10.0-py3-none-any.whl", hash = "sha256:d5350311567ae660c24cbc1752aee4b3d660f89c0106d2dcd2a69978c35afe1e"}, - {file = "google_genai-2.10.0.tar.gz", hash = "sha256:77912cd558cd7dfd5b75c25fd1c609e78d7954dde583331104022a46ea90f9ee"}, + {file = "google_genai-2.14.0-py3-none-any.whl", hash = "sha256:ae7172cdd35695189b516b33a878e4132e5daa2dbc03a5b44cddfa8a82fad664"}, + {file = "google_genai-2.14.0.tar.gz", hash = "sha256:a9d1f4f362d76280f1be1340fcb3c86e63dbca128f6a4ae09d86ab47ff7148e8"}, ] [package.dependencies] anyio = ">=4.8.0,<5.0.0" distro = ">=1.7.0,<2" -google-auth = {version = ">=2.48.1,<3.0.0", extras = ["requests"]} +google-auth = {version = ">=2.56.0,<3.0.0", extras = ["requests"]} httpx = ">=0.28.1,<1.0.0" pydantic = ">=2.12.5,<3.0.0" requests = ">=2.28.1,<3.0.0" @@ -2229,40 +2156,16 @@ aiohttp = ["aiohttp (>=3.10.11,<4.0.0)"] local-tokenizer = ["pillow", "protobuf", "sentencepiece (>=0.2.0)", "torch", "torchvision", "transformers"] pyopenssl = ["pyopenssl"] -[[package]] -name = "google-generativeai" -version = "0.8.6" -description = "Google Generative AI High level API client library and tools." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "google_generativeai-0.8.6-py3-none-any.whl", hash = "sha256:37a0eaaa95e5bbf888828e20a4a1b2c196cc9527d194706e58a68ff388aeb0fa"}, -] - -[package.dependencies] -google-ai-generativelanguage = "0.6.15" -google-api-core = "*" -google-api-python-client = "*" -google-auth = ">=2.15.0" -protobuf = "*" -pydantic = "*" -tqdm = "*" -typing-extensions = "*" - -[package.extras] -dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"] - [[package]] name = "googleapis-common-protos" -version = "1.74.0" +version = "1.75.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5"}, - {file = "googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1"}, + {file = "googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed"}, + {file = "googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd"}, ] [package.dependencies] @@ -2288,97 +2191,70 @@ grpcio = "*" [[package]] name = "grpcio" -version = "1.80.0" +version = "1.83.0" description = "HTTP/2-based RPC framework" optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "grpcio-1.80.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:886457a7768e408cdce226ad1ca67d2958917d306523a0e21e1a2fdaa75c9c9c"}, - {file = "grpcio-1.80.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7b641fc3f1dc647bfd80bd713addc68f6d145956f64677e56d9ebafc0bd72388"}, - {file = "grpcio-1.80.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33eb763f18f006dc7fee1e69831d38d23f5eccd15b2e0f92a13ee1d9242e5e02"}, - {file = "grpcio-1.80.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:52d143637e3872633fc7dd7c3c6a1c84e396b359f3a72e215f8bf69fd82084fc"}, - {file = "grpcio-1.80.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c51bf8ac4575af2e0678bccfb07e47321fc7acb5049b4482832c5c195e04e13a"}, - {file = "grpcio-1.80.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:50a9871536d71c4fba24ee856abc03a87764570f0c457dd8db0b4018f379fed9"}, - {file = "grpcio-1.80.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a72d84ad0514db063e21887fbacd1fd7acb4d494a564cae22227cd45c7fbf199"}, - {file = "grpcio-1.80.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7691a6788ad9196872f95716df5bc643ebba13c97140b7a5ee5c8e75d1dea81"}, - {file = "grpcio-1.80.0-cp310-cp310-win32.whl", hash = "sha256:46c2390b59d67f84e882694d489f5b45707c657832d7934859ceb8c33f467069"}, - {file = "grpcio-1.80.0-cp310-cp310-win_amd64.whl", hash = "sha256:dc053420fc75749c961e2a4c906398d7c15725d36ccc04ae6d16093167223b58"}, - {file = "grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a"}, - {file = "grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060"}, - {file = "grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2"}, - {file = "grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21"}, - {file = "grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab"}, - {file = "grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1"}, - {file = "grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106"}, - {file = "grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6"}, - {file = "grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440"}, - {file = "grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9"}, - {file = "grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0"}, - {file = "grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2"}, - {file = "grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de"}, - {file = "grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921"}, - {file = "grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411"}, - {file = "grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd"}, - {file = "grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f"}, - {file = "grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f"}, - {file = "grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193"}, - {file = "grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff"}, - {file = "grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad"}, - {file = "grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0"}, - {file = "grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f"}, - {file = "grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6"}, - {file = "grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140"}, - {file = "grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d"}, - {file = "grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7"}, - {file = "grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7"}, - {file = "grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294"}, - {file = "grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50"}, - {file = "grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e"}, - {file = "grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f"}, - {file = "grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9"}, - {file = "grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14"}, - {file = "grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05"}, - {file = "grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1"}, - {file = "grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f"}, - {file = "grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e"}, - {file = "grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae"}, - {file = "grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f"}, - {file = "grpcio-1.80.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:aacdfb4ed3eb919ca997504d27e03d5dba403c85130b8ed450308590a738f7a4"}, - {file = "grpcio-1.80.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:a361c20ec1ccd3c3953d20fb6d7b4125093bdd10dff44c5e2bbb39e58917cedc"}, - {file = "grpcio-1.80.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:43168871f170d1e4ed16ae03d10cd21efa29f190e710a624cee7e5ae07da6f4f"}, - {file = "grpcio-1.80.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1b97cd29a8eda100b559b455331c487a80915b6ea6bd91cf3e89836c4ee8d957"}, - {file = "grpcio-1.80.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bac1d573dfa84ce59a5547073e28fa7326d53352adda6912e362da0b917fcef4"}, - {file = "grpcio-1.80.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4560cf0e86514595dbbd330cd65b7afad4b5c4b8c4905c041cfffa138d45e6fd"}, - {file = "grpcio-1.80.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec0a592e926071b4abad50c1495cd0d0d513324b3ff5e7267067c33ba27506e4"}, - {file = "grpcio-1.80.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:deb10a1528473c11f72a0939eed36d83e847d7cbb63e8cc5611fb7a912d38614"}, - {file = "grpcio-1.80.0-cp39-cp39-win32.whl", hash = "sha256:627fb7312171cdc52828bd6fac8d7028ff2a64b89f1957b6f3416caa2218d141"}, - {file = "grpcio-1.80.0-cp39-cp39-win_amd64.whl", hash = "sha256:05d55e1798756282cddd52d56c896b3e7d673e3a8798c2f1cd05ba249a3bb4de"}, - {file = "grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257"}, + {file = "grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8"}, + {file = "grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727"}, + {file = "grpcio-1.83.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5882c1a721b50ce0123ee5e839e1ab059ad72a7ade76cdf2d5bd833b56791acf"}, + {file = "grpcio-1.83.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e3eedfc92b6b9f2960115e7e620cf0cbf80bb7849a51ce3820dc54dfd88b6b9"}, + {file = "grpcio-1.83.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fcaa7c45c45b4a89e2867d1f1785d9481a788399d915e341ed2eb49aeef9dd4"}, + {file = "grpcio-1.83.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6b6c666a1d5613ff360c9e90f44665e3a88b25a815209ddbc0917eec281931cb"}, + {file = "grpcio-1.83.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6be5c807b717be3dd649446f021301fd7907e376318675d2147823071034112a"}, + {file = "grpcio-1.83.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c834e86d8fd2f03d7e4db49a027f7c5b89c5b88eed305543a5295bd6fee61e40"}, + {file = "grpcio-1.83.0-cp310-cp310-win32.whl", hash = "sha256:35a5b1c192496b6c25956eebfa963468935612206fd2543ac3ce981e6a5e0f03"}, + {file = "grpcio-1.83.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f6c395e493d20c39b29392ca200e9aaeb78d0bc2f04db0c0a7da7ddc939aa57"}, + {file = "grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f"}, + {file = "grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223"}, + {file = "grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0"}, + {file = "grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d"}, + {file = "grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9"}, + {file = "grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745"}, + {file = "grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617"}, + {file = "grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969"}, + {file = "grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45"}, + {file = "grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49"}, + {file = "grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930"}, + {file = "grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da"}, + {file = "grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16"}, + {file = "grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf"}, + {file = "grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd"}, + {file = "grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c"}, + {file = "grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5"}, + {file = "grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5"}, + {file = "grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc"}, + {file = "grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df"}, + {file = "grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0"}, + {file = "grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1"}, + {file = "grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867"}, + {file = "grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881"}, + {file = "grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f"}, + {file = "grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61"}, + {file = "grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45"}, + {file = "grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c"}, + {file = "grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf"}, + {file = "grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735"}, + {file = "grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa"}, + {file = "grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c"}, + {file = "grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b"}, + {file = "grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c"}, + {file = "grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df"}, + {file = "grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b"}, + {file = "grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404"}, + {file = "grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af"}, + {file = "grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33"}, + {file = "grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9"}, + {file = "grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24"}, ] [package.dependencies] typing-extensions = ">=4.12,<5.0" [package.extras] -protobuf = ["grpcio-tools (>=1.80.0)"] - -[[package]] -name = "grpcio-status" -version = "1.71.2" -description = "Status proto mapping for gRPC" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3"}, - {file = "grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.71.2" -protobuf = ">=5.26.1,<6.0.dev0" +protobuf = ["grpcio-tools (>=1.83.0)"] [[package]] name = "gunicorn" @@ -2417,38 +2293,30 @@ files = [ [[package]] name = "hf-xet" -version = "1.4.3" +version = "1.5.2" description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" groups = ["main"] markers = "python_version < \"3.14\" and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" files = [ - {file = "hf_xet-1.4.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7551659ba4f1e1074e9623996f28c3873682530aee0a846b7f2f066239228144"}, - {file = "hf_xet-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bee693ada985e7045997f05f081d0e12c4c08bd7626dc397f8a7c487e6c04f7f"}, - {file = "hf_xet-1.4.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21644b404bb0100fe3857892f752c4d09642586fd988e61501c95bbf44b393a3"}, - {file = "hf_xet-1.4.3-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:987f09cfe418237812896a6736b81b1af02a3a6dcb4b4944425c4c4fca7a7cf8"}, - {file = "hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:60cf7fc43a99da0a853345cf86d23738c03983ee5249613a6305d3e57a5dca74"}, - {file = "hf_xet-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2815a49a7a59f3e2edf0cf113ae88e8cb2ca2a221bf353fb60c609584f4884d4"}, - {file = "hf_xet-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:42ee323265f1e6a81b0e11094564fb7f7e0ec75b5105ffd91ae63f403a11931b"}, - {file = "hf_xet-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:27c976ba60079fb8217f485b9c5c7fcd21c90b0367753805f87cb9f3cdc4418a"}, - {file = "hf_xet-1.4.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5251d5ece3a81815bae9abab41cf7ddb7bcb8f56411bce0827f4a3071c92fdc6"}, - {file = "hf_xet-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1feb0f3abeacee143367c326a128a2e2b60868ec12a36c225afb1d6c5a05e6d2"}, - {file = "hf_xet-1.4.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b301fc150290ca90b4fccd079829b84bb4786747584ae08b94b4577d82fb791"}, - {file = "hf_xet-1.4.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d972fbe95ddc0d3c0fc49b31a8a69f47db35c1e3699bf316421705741aab6653"}, - {file = "hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5b48db1ee344a805a1b9bd2cda9b6b65fe77ed3787bd6e87ad5521141d317cd"}, - {file = "hf_xet-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22bdc1f5fb8b15bf2831440b91d1c9bbceeb7e10c81a12e8d75889996a5c9da8"}, - {file = "hf_xet-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:0392c79b7cf48418cd61478c1a925246cf10639f4cd9d94368d8ca1e8df9ea07"}, - {file = "hf_xet-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:681c92a07796325778a79d76c67011764ecc9042a8c3579332b61b63ae512075"}, - {file = "hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025"}, - {file = "hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583"}, - {file = "hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08"}, - {file = "hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f"}, - {file = "hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac"}, - {file = "hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba"}, - {file = "hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021"}, - {file = "hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47"}, - {file = "hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113"}, + {file = "hf_xet-1.5.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4a5ecb9cda8512ba2aa8ee5d37c87a1422992165892d653098c7b90247481c3b"}, + {file = "hf_xet-1.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8764488197c1d7b1378c8438c18d2eea902e150dbca0b0f0d2d32603fb9b5576"}, + {file = "hf_xet-1.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d7446f72abbf7e01ca5ff131786bc2e74a56393462c17a6bf1e303fbab81db4"}, + {file = "hf_xet-1.5.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:580e59e29bf37aece1f2b68537de1e3fb04f43a23d910dcf6f128280b5bfbba4"}, + {file = "hf_xet-1.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bee28c619622d36968056532fd49cf2b35ca75099b1d616c31a618a893491380"}, + {file = "hf_xet-1.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e396ab0faf6298199ad7a95305c3ca8498cb825978a6485be6d00587ee4ec577"}, + {file = "hf_xet-1.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fd3add255549e8ef58fa35b2e42dc016961c050600444e7d77d030ba6b57120e"}, + {file = "hf_xet-1.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d6f9c58549407b84b9a5383afd68db0acc42345326a3159990b36a5ca8a20e4e"}, + {file = "hf_xet-1.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f922b8f5fb84f1dd3d7ab7a1316354a1bca9b1c73ecfc19c76e51a2a49d29799"}, + {file = "hf_xet-1.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:045f84440c55cdeb659cf1a1dd48c77bcd0d2e93632e2fea8f2c3bdee79f38ed"}, + {file = "hf_xet-1.5.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db78c39c83d6279daddc98e2238f373ab8980685556d42472b4ec51abcf03e8c"}, + {file = "hf_xet-1.5.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7db73c810500c54c6760be8c39d4b2e476974de85424c50063efc22fdda13025"}, + {file = "hf_xet-1.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6395cfe3c9cbead4f16b31808b0e67eac428b66c656f856e99636adaddea878f"}, + {file = "hf_xet-1.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cde8cd167126bb6109b2ceb19b844433a4988643e8f3e01dd9dd0e4a34535097"}, + {file = "hf_xet-1.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:ecf63d1cb69a9a7319910f8f83fcf9b46e7a32dfcf4b8f8eeddb55f647306e65"}, + {file = "hf_xet-1.5.2-cp38-abi3-win_arm64.whl", hash = "sha256:1da28519496eb7c8094c11e4d25509b4a468457a0302d58136099db2fd9a671d"}, + {file = "hf_xet-1.5.2.tar.gz", hash = "sha256:73044bd31bae33c984af832d19c752a0dffb67518fee9ddbd91d616e1101cf47"}, ] [package.extras] @@ -2456,14 +2324,14 @@ tests = ["pytest"] [[package]] name = "holidays" -version = "0.100" +version = "0.101" description = "Open World Holidays Framework" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "holidays-0.100-py3-none-any.whl", hash = "sha256:cf67e8983c3af182b62081be27c965c6fdf41377026aa62ac82d1bbc8c7ebd8f"}, - {file = "holidays-0.100.tar.gz", hash = "sha256:76e963cd17758ba0e0519daff725f676eaebeda71e500cd235f647307be31e31"}, + {file = "holidays-0.101-py3-none-any.whl", hash = "sha256:be1d5bb0b662d709da08dcdfa9b42feb678312df2a5d6280421da71349e4757f"}, + {file = "holidays-0.101.tar.gz", hash = "sha256:fbaa802d66e5d24877359ae68c46d00a73437162d3e9a17466fe6cfcce123c3a"}, ] [package.dependencies] @@ -2493,14 +2361,14 @@ trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httplib2" -version = "0.31.2" +version = "0.32.0" description = "A comprehensive HTTP client library." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349"}, - {file = "httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24"}, + {file = "httplib2-0.32.0-py3-none-any.whl", hash = "sha256:dc6705cacdf3fb0a2aba7629fa33c90fd93e30035db0c157325826be177e4816"}, + {file = "httplib2-0.32.0.tar.gz", hash = "sha256:48a0ef30a42db65d8f3399045e1d09ab0ba66e3b9efc360d07f80ea55d286025"}, ] [package.dependencies] @@ -2545,26 +2413,26 @@ files = [ [[package]] name = "huggingface-hub" -version = "1.13.0" +version = "1.25.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.10.0" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "huggingface_hub-1.13.0-py3-none-any.whl", hash = "sha256:e942cb50d6a08dd5306688b1ac05bda157fd2fcc88b63dae405f7bd0d3234005"}, - {file = "huggingface_hub-1.13.0.tar.gz", hash = "sha256:f6df2dac5abe82ce2fe05873d10d5ff47bc677d616a2f521f4ee26db9415d9d0"}, + {file = "huggingface_hub-1.25.1-py3-none-any.whl", hash = "sha256:004d4e70350517e24c68a7dbb7dc5e40b2b6aefef8f94bf7a85f6f9835102ea5"}, + {file = "huggingface_hub-1.25.1.tar.gz", hash = "sha256:21129595ca7a753be479b319913e22cc8808361ac118bd76cc413db831b28a99"}, ] [package.dependencies] +click = ">=8.4.2,<9.0.0" filelock = ">=3.10.0" fsspec = ">=2023.5.0" -hf-xet = {version = ">=1.4.3,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} +hf-xet = {version = ">=1.5.1,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} httpx = ">=0.23.0,<1" packaging = ">=20.9" pyyaml = ">=5.1" tqdm = ">=4.42.1" -typer = "*" typing-extensions = ">=4.1.0" [package.extras] @@ -2572,7 +2440,7 @@ all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "duckdb", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] gradio = ["gradio (>=5.0.0)", "requests"] -hf-xet = ["hf-xet (>=1.4.3,<2.0.0)"] +hf-xet = ["hf-xet (>=1.5.1,<2.0.0)"] mcp = ["mcp (>=1.8.0)"] oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "ruff (>=0.9.0)", "ty"] @@ -2680,7 +2548,6 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.14\"" files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2694,121 +2561,117 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.15.0" +version = "0.16.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "jiter-0.15.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:edebcf7d1f601199084bb6e844d7dc67e03e04f6ac786b0332d616635c4ff7a4"}, - {file = "jiter-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f924585cdacf631cd382b657966847bb537bf9ed0a6f9b991da5f05a631480f"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abbf258599526ad0326fe51e252e24f2bd6f24f1852681b4b78feda3808f1d18"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c468136b8bd6bb18c8786e4236a1fa27362f24cb23450ba0cb204ab379b8e6f"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05906b93d72f03339e6bb7cf8dc10ebda64a0266126eed6beba79e20abcf5fd4"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30ce785d2adb8e32c3f7741442370a74834ec4c01f3c48f0750227a0b4ef27d6"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd73e3da91a0a722d67165e849ce2cdc10de0e0d48738c142be8c6c5f310f4c"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:ceb8fc27d38793f9c97149be8302720c5b22e5c195a37bf2c45dc36c4600a512"}, - {file = "jiter-0.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d726e3ceeb337191324b49de298142f27c3ad10886341555d1d5315b5f252c6a"}, - {file = "jiter-0.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c8aea7781d2a372227871de4e1a1332aa96f5a89fd76c5e835dafdbad102887"}, - {file = "jiter-0.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf4bd113a69c0a740e27cb962ce10630c36d2b8f59d759a651b955ee9d18a823"}, - {file = "jiter-0.15.0-cp310-cp310-win32.whl", hash = "sha256:d92a5cd21fdb083931d546c207aa29633787c5dc5b02daab2d32b843f88a2c53"}, - {file = "jiter-0.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:e58585a58209d72691ce2d62a9147445f5a87beb0bde97fde284c96ae392a3d1"}, - {file = "jiter-0.15.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0f862193b8696249d22ec433e85fd2ab0ad9596bc3e45e6c0bc55e8aeba97be2"}, - {file = "jiter-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1303d4d68a9b051ea90502402063ecf3807da00ad2affa19ca1ae3b90b3c5f67"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392b8ab019e5502d08aff85c6272209c24bc2cbe706ea82a56368f524236614a"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:773b6eb282ce11ee19f05f6b2d4404fa308e5bbd353b0b80a0262caad6db2cd7"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2c0c44d569ce0f2850f5c926f8caeb5f245fbc84475aeb36efccc2103e6dbd"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:032396229564bca02440396bd327710719f724f5e7b7e9f7a8eb3faa4a2c2281"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d37768fce7f88dd2a8c6091f2325dea27d30d30d5c6e7a1c0f0af77723b708"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2c9cb907439d20bd0c7d7565ca01ee52234203208433749bae5b516907526928"}, - {file = "jiter-0.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9100ddbec09741cc66feb0fc6773f8bdbd0e3c345689368f260082ff85dcc0cd"}, - {file = "jiter-0.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae1b0d82ac2d987f9ea512b1c9adfcc71a28de3dea3a6039b54d76cffda9901e"}, - {file = "jiter-0.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8020c99ec13a7db2b6f96cbe82ef4721c88b426a4892f27478044af0284615ef"}, - {file = "jiter-0.15.0-cp311-cp311-win32.whl", hash = "sha256:42bfb257930800cf43e7c62c832402c704ab60797c992faf88d20e903eac8f32"}, - {file = "jiter-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:860a74063284a2ae9bfedd694f299cc2c68e2696c5f3d440cc9d18bb81b9dd04"}, - {file = "jiter-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:37a10c377ce3a4a85f4a67f28b7afe093154cde77eaf248a72e856aa08b4d865"}, - {file = "jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d"}, - {file = "jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb"}, - {file = "jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871"}, - {file = "jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77"}, - {file = "jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d"}, - {file = "jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d"}, - {file = "jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7"}, - {file = "jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b"}, - {file = "jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3"}, - {file = "jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29"}, - {file = "jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b"}, - {file = "jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7"}, - {file = "jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712"}, - {file = "jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c"}, - {file = "jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0"}, - {file = "jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba"}, - {file = "jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8"}, - {file = "jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c"}, - {file = "jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4"}, - {file = "jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b"}, - {file = "jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7"}, - {file = "jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49"}, - {file = "jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd"}, - {file = "jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89"}, - {file = "jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554"}, - {file = "jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a"}, - {file = "jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec"}, - {file = "jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558"}, - {file = "jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866"}, - {file = "jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8"}, - {file = "jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec"}, - {file = "jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e"}, - {file = "jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5"}, - {file = "jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52"}, - {file = "jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854"}, - {file = "jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0"}, - {file = "jiter-0.15.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:04b400bbf8c9efb03d9bdd976475c919c1d85593b04b9fff7ae234065daf87ae"}, - {file = "jiter-0.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25ffbe229aa8cd98c28879d8aa1a6e34ae77992ab984a65fba800859dab16269"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5607e6013ed7e6b0ec9661e467b7ffde0aa7ab36833a04850f26fcf88ed4845b"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50164d7610c00e7cd913a873fce30b6beeebf4b37e53983e33f22de4c900f6b8"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ab596fa3837e91e7e6a31b5f639988bfc6a35d1f915ac3932d946062219d588f"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72d8af5c1013656a8870c866660627d1a75bc185814ee022c8533caa1de88ae"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c84c1b7be454b0c16f8499b4ebfbfd82ea5cca6527cceefcbbc06a7557b5ed2e"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:d636d5095155afd364247f65070fab7beda13498d7ff4de331046e704ab9657f"}, - {file = "jiter-0.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d3d6683288c11cbab50e865f2e2f13950179aa45410e30b2cfbd3fb7b0177bf"}, - {file = "jiter-0.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7ce8902f939970048b233087082e7bb829db29375811c7ad50687b8624c6fd08"}, - {file = "jiter-0.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4363818355dbc70ae1a8e9eaba9de350d93ede4ff6992b8f8eb8cbb6e5122d42"}, - {file = "jiter-0.15.0-cp39-cp39-win32.whl", hash = "sha256:8f7e9bc0f1135039b22ee6eab588d42df1ce55842b30740a352885eb267bd941"}, - {file = "jiter-0.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:1c15024a3d892223b18f597c86d59387249dc396590844ce6b9f6131d1093bae"}, - {file = "jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:411fa4dfa5a7ae3d11491027ffb9beadec3996010a986862db70d91abba1c750"}, - {file = "jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:2b0074e2f56eb2dacca1689760fd2852a068f85a0547a157b82cb4cafeb6768b"}, - {file = "jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913d02d29c9606643418d9ccfc3b72492ab25a6bf7889934e09a3490f8d3438b"}, - {file = "jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15d3ec9b0449c40e85319bdb4caa8b77ab526e74f5532ed94bec15e2f66822c"}, - {file = "jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0"}, - {file = "jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45"}, - {file = "jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c"}, - {file = "jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a"}, - {file = "jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76"}, + {file = "jiter-0.16.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fc4f8def331036a7b8e981b4347ebe409981edbc8308a5ea842b8c3614fa6c"}, + {file = "jiter-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a71d0d2014c3275043e1170bf3d4e771493cb0dcf07be54c567155f4d8ee64b"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:741eed508c233a76313a1c7b001f8f21b82f14327e9196ae8bd29a2cc164ae84"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fb7bc819187b56dc48aa5c833aaf92257da8e07efdb9306156667bd2eeb491c"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9610fd25ebccb43fca584136f5c2fbb26802447eccd430dfdbab95a0fd5126"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a1d68ff7ca1d3b5dee20a97a3decda7d5f15003823bf6d140c81f8561d3bc5c"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb08c276dd02dac3a284acdd02cacc630d2e3cd6572a4b85519f35cbd133c3de"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:8fc4d94713c4697347e38faf7d6ef91547c142219bdcfc7220c4870879974244"}, + {file = "jiter-0.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0f05e229edb29e68cdd0ccb83cea13b64263416120cf943767a6fd72e6787f"}, + {file = "jiter-0.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c842cbf374a8daf50b2c04212995bee34ca2ac2cdc29a901b4cdb072c9c4131"}, + {file = "jiter-0.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ed466aee31294d7cdcd4d37dfe5c42c97bc29d9a5f00eacf24504358309cb9b"}, + {file = "jiter-0.16.0-cp310-cp310-win32.whl", hash = "sha256:b42e9ff5376819c053da25809a8d4b6fa6e473b4856ebe42e298ac958be3d7f9"}, + {file = "jiter-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:10438939205546132189c8e74a2d536a707841f3a25cd7c74ee91fe503407a26"}, + {file = "jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3"}, + {file = "jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03"}, + {file = "jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea"}, + {file = "jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91"}, + {file = "jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3"}, + {file = "jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7"}, + {file = "jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1"}, + {file = "jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056"}, + {file = "jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a"}, + {file = "jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9"}, + {file = "jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5"}, + {file = "jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730"}, + {file = "jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f"}, + {file = "jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274"}, + {file = "jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7"}, + {file = "jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331"}, + {file = "jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195"}, + {file = "jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053"}, + {file = "jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e"}, + {file = "jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb"}, + {file = "jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84"}, + {file = "jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e"}, + {file = "jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd"}, + {file = "jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a"}, + {file = "jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee"}, + {file = "jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29"}, + {file = "jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93"}, + {file = "jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a"}, + {file = "jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00"}, + {file = "jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe"}, + {file = "jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106"}, + {file = "jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8"}, + {file = "jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585"}, + {file = "jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af"}, + {file = "jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e"}, + {file = "jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077"}, + {file = "jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734"}, + {file = "jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf"}, + {file = "jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db"}, + {file = "jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce"}, + {file = "jiter-0.16.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d8f80521644426d451e70f00c7974240cab8f6ee088aedaa9af2697153ab7805"}, + {file = "jiter-0.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b21b412b899fd8bd51a3046934b59a3bb068b79f70a5c6010053ac77cc53f0c"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0758ab7747a984797cf048e8eedea1d8ef39d7994b25611daf5b48fc903e8873"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ec553a99b0987efd7a3645a1a825cf29c224e494db267a83369fcc8da9aeda5"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3bd327cdfa118bc1ce69c214c2678571d5bd39b8ccd0ebf43a54db00541ba9a"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26d122613ada2b708eb714695446f40fce5bdf2edb4b02116dec62faa62dfab3"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e03a5f21a5ce96a9441b8cb32719a8b88ed5388f53e0f339c5bcf54f1317f9d0"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:a5c54ef4ff776d9675837ef535b3308d6e31c208d43ebc44a0f7ab8a208c68f7"}, + {file = "jiter-0.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1e7923093a376d93c6eb507c77045ae258d689ba577392846a1b3f10d0b09a9"}, + {file = "jiter-0.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2a0d46ef67cc58d906a6132dd3040ca70ae4f0b0d7c9c052fe432c658a69b3f6"}, + {file = "jiter-0.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70a490b55634dc0d2606ce8a8e01b1d62459011beb368d15d76e1eaf62460e3d"}, + {file = "jiter-0.16.0-cp39-cp39-win32.whl", hash = "sha256:9acf1b2faec82d998811ecce7ae84d9005e53410773e9d37d61cdc424ba4581b"}, + {file = "jiter-0.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:491e7d072a253b156fff46b78bceac4652a697aa8d7082c9c18c03d7b7917d24"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5"}, + {file = "jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702"}, + {file = "jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2"}, + {file = "jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c"}, ] [[package]] @@ -3124,116 +2987,127 @@ rapidfuzz = ">=3.9.0,<4.0.0" [[package]] name = "librt" -version = "0.11.0" +version = "0.13.0" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"}, - {file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"}, - {file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"}, - {file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"}, - {file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"}, - {file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"}, - {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"}, - {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"}, - {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"}, - {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"}, - {file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"}, - {file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"}, - {file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"}, - {file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"}, - {file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"}, - {file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"}, - {file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"}, - {file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"}, - {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"}, - {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"}, - {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"}, - {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"}, - {file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"}, - {file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"}, - {file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"}, - {file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"}, - {file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"}, - {file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"}, - {file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"}, - {file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"}, - {file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"}, - {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"}, - {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"}, - {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"}, - {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"}, - {file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"}, - {file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"}, - {file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"}, - {file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"}, - {file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"}, - {file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"}, - {file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"}, - {file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"}, - {file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"}, - {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"}, - {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"}, - {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"}, - {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"}, - {file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"}, - {file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"}, - {file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"}, - {file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"}, - {file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"}, - {file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"}, - {file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"}, - {file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"}, - {file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"}, - {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"}, - {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"}, - {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"}, - {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"}, - {file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"}, - {file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"}, - {file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"}, - {file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"}, - {file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"}, - {file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"}, - {file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"}, - {file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"}, - {file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"}, - {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"}, - {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"}, - {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"}, - {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"}, - {file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"}, - {file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"}, - {file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"}, - {file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"}, - {file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"}, - {file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"}, - {file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"}, - {file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"}, - {file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"}, - {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"}, - {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"}, - {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"}, - {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"}, - {file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"}, - {file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"}, - {file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"}, + {file = "librt-0.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5"}, + {file = "librt-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547"}, + {file = "librt-0.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2"}, + {file = "librt-0.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929"}, + {file = "librt-0.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a"}, + {file = "librt-0.13.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac"}, + {file = "librt-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7"}, + {file = "librt-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40"}, + {file = "librt-0.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a"}, + {file = "librt-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde"}, + {file = "librt-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8"}, + {file = "librt-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc"}, + {file = "librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082"}, + {file = "librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14"}, + {file = "librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79"}, + {file = "librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176"}, + {file = "librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89"}, + {file = "librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f"}, + {file = "librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d"}, + {file = "librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd"}, + {file = "librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588"}, + {file = "librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1"}, + {file = "librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21"}, + {file = "librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b"}, + {file = "librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c"}, + {file = "librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0"}, + {file = "librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5"}, + {file = "librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9"}, + {file = "librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b"}, + {file = "librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03"}, + {file = "librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e"}, + {file = "librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd"}, + {file = "librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348"}, + {file = "librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7"}, + {file = "librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82"}, + {file = "librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3"}, + {file = "librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa"}, + {file = "librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1"}, + {file = "librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3"}, + {file = "librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c"}, + {file = "librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c"}, + {file = "librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b"}, + {file = "librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9"}, + {file = "librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db"}, + {file = "librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6"}, + {file = "librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7"}, + {file = "librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1"}, + {file = "librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a"}, + {file = "librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628"}, + {file = "librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927"}, + {file = "librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650"}, + {file = "librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566"}, + {file = "librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71"}, + {file = "librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6"}, + {file = "librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f"}, + {file = "librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180"}, + {file = "librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6"}, + {file = "librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9"}, + {file = "librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007"}, + {file = "librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0"}, + {file = "librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1"}, + {file = "librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d"}, + {file = "librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16"}, + {file = "librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37"}, + {file = "librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39"}, + {file = "librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6"}, + {file = "librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5"}, + {file = "librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46"}, + {file = "librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e"}, + {file = "librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a"}, + {file = "librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22"}, + {file = "librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c"}, + {file = "librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0"}, + {file = "librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04"}, + {file = "librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61"}, + {file = "librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9"}, + {file = "librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18"}, + {file = "librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259"}, + {file = "librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99"}, + {file = "librt-0.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f442e3954b1addc759faae22a7c9a3f1e16d7d1db3f484279dc27d62e06968fa"}, + {file = "librt-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e786428f291dd2d2f1cbfc0e0caa45a2e395fab0ad3e2c9314daa8873414390"}, + {file = "librt-0.13.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21b7ac084f701a9cdff6139745a6620579d65a9379ac2d9d50a86368b109e63c"}, + {file = "librt-0.13.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a6e556d6aba31c93dd97ce661d66614d2429c0a3923f9dc8f0af7e8df10223a4"}, + {file = "librt-0.13.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3657346f867469e962549435aa05fd15330b1d6a92829f8e27988e194382d005"}, + {file = "librt-0.13.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:791aa18a373b90da8ac3c44fc77544f33fdf53ae403acdce9b39f1c26b4a3b94"}, + {file = "librt-0.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d6fb0eaa108814581c4d3bfbd068c3fb6757812a81415008d1bae08267cca360"}, + {file = "librt-0.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a001519c315d5db40710f2665d32c4791f1d4779fc96a9423fd18d92c8b9ac7b"}, + {file = "librt-0.13.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:d9188caac26e47671b52836a5e2a49873a7fc11c673b0c122d22515f98bc14e1"}, + {file = "librt-0.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:05d96b80b95d3a2721b619f8982b8558848b04875bb4772fd54842b59f61dd97"}, + {file = "librt-0.13.0-cp39-cp39-win32.whl", hash = "sha256:c3cd253cf32fe4f4662960d6bf7d55cb8be0c31a5d644a4d48aeafebaff3409a"}, + {file = "librt-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:b15e26cc0fe622d0c67e98bee6ef6bc8f792e20ee3006aa12627a00463d9399f"}, + {file = "librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781"}, ] [[package]] name = "litellm" -version = "1.88.1" +version = "1.93.0" description = "Library to easily interface with LLM API providers" optional = false -python-versions = "<3.14,>=3.10" +python-versions = "<3.15,>=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "litellm-1.88.1-py3-none-any.whl", hash = "sha256:369b84e57d9426582ddc35e731956ddb6618cda97cc44e4e4d2dfa75982a6e3a"}, - {file = "litellm-1.88.1.tar.gz", hash = "sha256:89c6b74cc7912d6365793006ff951c0450fe847625008dfe49de8a7dc4529aa5"}, + {file = "litellm-1.93.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5d84aead083d3d0619b2d293f8006ebbe75611bbdcef470d659c9e2d131d4454"}, + {file = "litellm-1.93.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6b49f653e6205cbc725257da8797277f77065c69865fb3489bc5ed0896b66ee0"}, + {file = "litellm-1.93.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:952029d422c2cec8117b444d66d6de87ac970ecf0b2e1383b02788ef90ba3603"}, + {file = "litellm-1.93.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:460001b7c88c5b6675ad482ae3cecba96e21f890604b4febb1a67807ace1c750"}, + {file = "litellm-1.93.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3daf5c5aceb07f5d68871071e0ecdc678caccebadca9143cc00bb76f5a8c54e8"}, + {file = "litellm-1.93.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0784172435de48f66ef7ad89d421604db1ad0db1321ef7f39dbcfe6b20111417"}, + {file = "litellm-1.93.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:23b8eea4fb8b3b6ade05e7b6085ec9ca12daffcfb38d033d0bbce9c1d5894da5"}, + {file = "litellm-1.93.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:98c15e84d32e922a821c105308bf9ddae8700de9ed396530bee2cbb1cacf4cbb"}, + {file = "litellm-1.93.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:1a476ebc340c070c982eab15b4673fa63a70f5936935f9f20e4d2acb5f35d23b"}, + {file = "litellm-1.93.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:cd70ccd4ba3ef1a395535c287bce72d30c7d937ecca4290c0db37a00738f7ada"}, + {file = "litellm-1.93.0.tar.gz", hash = "sha256:140bf215e264c71601bca9c06d2436c5451bb59e1e195ea23fc2d3d87b6929ec"}, ] [package.dependencies] @@ -3252,158 +3126,159 @@ tokenizers = ">=0.21.0,<1.0" [package.extras] caching = ["diskcache (>=5.6.3,<6.0)"] -extra-proxy = ["a2a-sdk (>=0.3.24,<1.0)", "azure-identity (>=1.25.2,<2.0)", "azure-keyvault-secrets (>=4.10.0,<5.0)", "google-cloud-iam (>=2.19.1,<3.0)", "google-cloud-kms (>=2.24.2,<3.0)", "prisma (>=0.11.0,<1.0)", "redisvl (>=0.4.1,<1.0) ; python_full_version < \"3.14.0\"", "resend (>=2.23.0,<3.0)"] +cli = ["pyyaml (>=6.0.3,<7.0)", "requests (>=2.32.0,<3.0)", "rich (>=13.9.4,<14.0)"] +extra-proxy = ["a2a-sdk (>=1.1.0,<2.0)", "azure-identity (>=1.25.2,<2.0)", "azure-keyvault-secrets (>=4.10.0,<5.0)", "google-cloud-iam (>=2.19.1,<3.0)", "google-cloud-kms (>=2.24.2,<3.0)", "prisma (>=0.11.0,<1.0)", "redisvl (>=0.4.1,<1.0)", "resend (>=2.23.0,<3.0)"] google = ["google-cloud-aiplatform (>=1.133.0,<2.0)"] grpc = ["grpcio (==1.78.0)"] mlflow = ["mlflow (>=3.11.1,<4.0)"] -proxy = ["apscheduler (>=3.11.2,<4.0)", "azure-identity (>=1.25.2,<2.0)", "azure-storage-blob (>=12.28.0,<13.0)", "backoff (>=2.2.1,<3.0)", "boto3 (>=1.43.1,<2.0)", "cryptography (>=46.0.7,<47.0)", "fastapi (>=0.136.3,<1.0)", "fastapi-sso (>=0.19.0,<1.0)", "granian (>=2.7.4,<3.0)", "gunicorn (>=23.0.0,<24.0)", "litellm-enterprise (==0.1.42)", "litellm-proxy-extras (==0.4.73)", "mcp (>=1.26.0,<2.0)", "orjson (>=3.11.6,<4.0)", "polars (>=1.38.1,<2.0)", "pydantic-settings (>=2.14.1,<3.0)", "pyjwt (>=2.13.0,<3.0)", "pynacl (>=1.6.2,<2.0)", "pyroscope-io (>=0.8.16,<1.0) ; sys_platform != \"win32\"", "python-multipart (>=0.0.27,<1.0)", "pyyaml (>=6.0.3,<7.0)", "restrictedpython (>=8.1,<9.0)", "rich (>=13.9.4,<14.0)", "rq (>=2.7.0,<3.0)", "soundfile (>=0.12.1,<1.0)", "starlette (>=1.0.1,<2.0)", "uvicorn (>=0.33.0,<1.0)", "uvloop (>=0.21.0,<1.0) ; sys_platform != \"win32\"", "websockets (>=15.0.1,<16.0)"] -proxy-runtime = ["anthropic[vertex] (>=0.84.0,<1.0)", "azure-ai-contentsafety (>=1.0.0,<2.0)", "azure-storage-file-datalake (>=12.20.0,<13.0)", "ddtrace (>=2.19.0,<3.0)", "detect-secrets (>=1.5.0,<2.0)", "google-cloud-aiplatform (>=1.133.0,<2.0)", "google-genai (>=1.37.0,<2.0)", "grpcio (==1.78.0)", "langfuse (>=2.59.7,<3.0)", "llm-sandbox (>=0.3.39,<1.0)", "mangum (>=0.17.0,<1.0)", "opentelemetry-api (==1.28.0)", "opentelemetry-exporter-otlp (==1.28.0)", "opentelemetry-instrumentation-fastapi (==0.49b0)", "opentelemetry-sdk (==1.28.0)", "prometheus-client (>=0.20.0,<1.0)", "pypdf (>=6.10.2,<7.0) ; python_full_version < \"3.14.0\"", "sentry-sdk (>=2.21.0,<3.0)"] +proxy = ["apscheduler (>=3.11.2,<4.0)", "azure-identity (>=1.25.2,<2.0)", "azure-storage-blob (>=12.28.0,<13.0)", "backoff (>=2.2.1,<3.0)", "boto3 (>=1.43.1,<2.0)", "cryptography (>=48.0.1,<49.0)", "expression (>=5.6.0,<6.0)", "fastapi (>=0.136.3,<1.0)", "fastapi-sso (>=0.19.0,<1.0)", "granian (>=2.7.4,<3.0)", "gunicorn (>=23.0.0,<24.0)", "litellm-enterprise (==0.1.49)", "litellm-proxy-extras (==0.4.76)", "mcp (>=1.28.1,<2.0)", "orjson (>=3.11.6,<4.0)", "polars (>=1.38.1,<2.0)", "pydantic-settings (>=2.14.1,<3.0)", "pyjwt (>=2.13.0,<3.0)", "pynacl (>=1.6.2,<2.0)", "pyroscope-io (>=0.8.16,<1.0) ; sys_platform != \"win32\"", "python-multipart (>=0.0.27,<1.0)", "pyyaml (>=6.0.3,<7.0)", "restrictedpython (>=8.1,<9.0)", "rich (>=13.9.4,<14.0)", "rq (>=2.7.0,<3.0)", "soundfile (>=0.12.1,<1.0)", "starlette (>=1.0.1,<2.0)", "uvicorn (>=0.33.0,<1.0)", "uvloop (>=0.21.0,<1.0) ; sys_platform != \"win32\"", "websockets (>=15.0.1,<16.0)"] +proxy-runtime = ["anthropic[vertex] (>=0.84.0,<1.0)", "azure-ai-contentsafety (>=1.0.0,<2.0)", "azure-storage-file-datalake (>=12.20.0,<13.0)", "ddtrace (>=4.8.2,<5.0)", "detect-secrets (>=1.5.0,<2.0)", "google-cloud-aiplatform (>=1.133.0,<2.0)", "google-genai (>=1.37.0,<2.0)", "grpcio (==1.78.0)", "langfuse (>=2.59.7,<3.0)", "llm-sandbox (>=0.3.39,<1.0)", "mangum (>=0.17.0,<1.0)", "opentelemetry-api (==1.28.0)", "opentelemetry-exporter-otlp (==1.28.0)", "opentelemetry-instrumentation-fastapi (==0.49b0)", "opentelemetry-sdk (==1.28.0)", "prometheus-client (>=0.20.0,<1.0)", "pypdf (>=6.12.0,<7.0)", "sentry-sdk (>=2.21.0,<3.0)"] semantic-router = ["aurelio-sdk (>=0.0.19,<1.0) ; python_full_version < \"3.14.0\"", "semantic-router (>=0.1.15,<1.0) ; python_full_version < \"3.14.0\""] stt-nvidia-riva = ["audioread (>=3.0.1)", "numpy (>=1.26.0)", "nvidia-riva-client (>=2.15.0)", "soundfile (>=0.12.1)"] utils = ["numpydoc (>=1.8.0,<2.0)"] [[package]] name = "lxml" -version = "6.1.0" +version = "6.1.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec"}, - {file = "lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6"}, - {file = "lxml-6.1.0-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3"}, - {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7"}, - {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16"}, - {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1"}, - {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a"}, - {file = "lxml-6.1.0-cp310-cp310-win32.whl", hash = "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544"}, - {file = "lxml-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a"}, - {file = "lxml-6.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776"}, - {file = "lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9"}, - {file = "lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289"}, - {file = "lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a"}, - {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3"}, - {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9"}, - {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11"}, - {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4"}, - {file = "lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3"}, - {file = "lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7"}, - {file = "lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39"}, - {file = "lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d"}, - {file = "lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad"}, - {file = "lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54"}, - {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d"}, - {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69"}, - {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d"}, - {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5"}, - {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d"}, - {file = "lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f"}, - {file = "lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366"}, - {file = "lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819"}, - {file = "lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45"}, - {file = "lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe"}, - {file = "lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88"}, - {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181"}, - {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24"}, - {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e"}, - {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495"}, - {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33"}, - {file = "lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62"}, - {file = "lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16"}, - {file = "lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d"}, - {file = "lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8"}, - {file = "lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292"}, - {file = "lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb"}, - {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad"}, - {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb"}, - {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f"}, - {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43"}, - {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585"}, - {file = "lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f"}, - {file = "lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120"}, - {file = "lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946"}, - {file = "lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c"}, - {file = "lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f"}, - {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773"}, - {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b"}, - {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405"}, - {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690"}, - {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd"}, - {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180"}, - {file = "lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2"}, - {file = "lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5"}, - {file = "lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac"}, - {file = "lxml-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940"}, - {file = "lxml-6.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd"}, - {file = "lxml-6.1.0-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13"}, - {file = "lxml-6.1.0-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919"}, - {file = "lxml-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72"}, - {file = "lxml-6.1.0-cp38-cp38-win32.whl", hash = "sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc"}, - {file = "lxml-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8"}, - {file = "lxml-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7"}, - {file = "lxml-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce"}, - {file = "lxml-6.1.0-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad"}, - {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39"}, - {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad"}, - {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383"}, - {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286"}, - {file = "lxml-6.1.0-cp39-cp39-win32.whl", hash = "sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618"}, - {file = "lxml-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2"}, - {file = "lxml-6.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037"}, - {file = "lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace"}, - {file = "lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13"}, + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60"}, + {file = "lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206"}, + {file = "lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383"}, + {file = "lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1"}, + {file = "lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a"}, + {file = "lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5"}, + {file = "lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2"}, + {file = "lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6"}, + {file = "lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c"}, + {file = "lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08"}, + {file = "lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621"}, + {file = "lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28"}, + {file = "lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7"}, + {file = "lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f"}, + {file = "lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955"}, + {file = "lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a"}, + {file = "lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a"}, + {file = "lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77"}, + {file = "lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736"}, + {file = "lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f"}, + {file = "lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785"}, + {file = "lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947"}, + {file = "lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca"}, + {file = "lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660"}, + {file = "lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0"}, + {file = "lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245"}, + {file = "lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590"}, + {file = "lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb"}, + {file = "lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603"}, + {file = "lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137"}, + {file = "lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee"}, + {file = "lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038"}, + {file = "lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2"}, + {file = "lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e"}, + {file = "lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1"}, + {file = "lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e"}, + {file = "lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c"}, + {file = "lxml-6.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3"}, + {file = "lxml-6.1.1-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4"}, + {file = "lxml-6.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e"}, + {file = "lxml-6.1.1-cp38-cp38-win32.whl", hash = "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3"}, + {file = "lxml-6.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d"}, + {file = "lxml-6.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8"}, + {file = "lxml-6.1.1-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf"}, + {file = "lxml-6.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d"}, + {file = "lxml-6.1.1-cp39-cp39-win32.whl", hash = "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186"}, + {file = "lxml-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730"}, + {file = "lxml-6.1.1-cp39-cp39-win_arm64.whl", hash = "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf"}, + {file = "lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84"}, + {file = "lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40"}, ] [package.extras] @@ -3412,31 +3287,6 @@ html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -[[package]] -name = "markdown-it-py" -version = "4.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins (>=0.5.0)"] -profiling = ["gprof2dot"] -rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] - [[package]] name = "markupsafe" version = "3.0.3" @@ -3444,7 +3294,6 @@ description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version < \"3.14\"" files = [ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, @@ -3539,83 +3388,71 @@ files = [ [[package]] name = "matplotlib" -version = "3.10.9" +version = "3.11.1" description = "Python plotting package" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217"}, - {file = "matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b"}, - {file = "matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37"}, - {file = "matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294"}, - {file = "matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65"}, - {file = "matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda"}, - {file = "matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb"}, - {file = "matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb"}, - {file = "matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb"}, - {file = "matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9"}, - {file = "matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb"}, - {file = "matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f"}, - {file = "matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80"}, - {file = "matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1"}, - {file = "matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320"}, - {file = "matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285"}, - {file = "matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2"}, - {file = "matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf"}, - {file = "matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6"}, - {file = "matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42"}, - {file = "matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f"}, - {file = "matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e"}, - {file = "matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f"}, - {file = "matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838"}, - {file = "matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2"}, - {file = "matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921"}, - {file = "matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8"}, - {file = "matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9"}, - {file = "matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4"}, - {file = "matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc"}, - {file = "matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99"}, - {file = "matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d"}, - {file = "matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8"}, - {file = "matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38"}, - {file = "matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d"}, - {file = "matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f"}, - {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b"}, - {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2"}, - {file = "matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716"}, - {file = "matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f"}, - {file = "matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456"}, - {file = "matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe"}, - {file = "matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6"}, - {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c"}, - {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4"}, - {file = "matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf"}, - {file = "matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39"}, - {file = "matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c"}, - {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b"}, - {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f"}, - {file = "matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585"}, - {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20"}, - {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba"}, - {file = "matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4"}, - {file = "matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358"}, + {file = "matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2"}, + {file = "matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c"}, + {file = "matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83"}, + {file = "matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099"}, + {file = "matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407"}, + {file = "matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f"}, + {file = "matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18"}, + {file = "matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74"}, + {file = "matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b"}, + {file = "matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea"}, + {file = "matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472"}, + {file = "matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481"}, + {file = "matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f"}, + {file = "matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a"}, + {file = "matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191"}, + {file = "matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1"}, + {file = "matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3"}, + {file = "matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e"}, + {file = "matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f"}, + {file = "matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b"}, + {file = "matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2"}, + {file = "matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f"}, + {file = "matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1"}, + {file = "matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464"}, + {file = "matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf"}, + {file = "matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f"}, + {file = "matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78"}, + {file = "matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a"}, + {file = "matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3"}, + {file = "matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319"}, + {file = "matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f"}, + {file = "matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be"}, + {file = "matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2"}, + {file = "matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6"}, + {file = "matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685"}, + {file = "matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae"}, + {file = "matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda"}, + {file = "matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb"}, + {file = "matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2"}, + {file = "matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d"}, + {file = "matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb"}, + {file = "matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987"}, + {file = "matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741"}, + {file = "matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb"}, + {file = "matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99"}, + {file = "matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30"}, ] [package.dependencies] contourpy = ">=1.0.1" cycler = ">=0.10" -fonttools = ">=4.22.0" +fonttools = ">=4.28.2" kiwisolver = ">=1.3.1" -numpy = ">=1.23" +numpy = ">=1.25" packaging = ">=20.0" -pillow = ">=8" +pillow = ">=9" pyparsing = ">=3" python-dateutil = ">=2.7" -[package.extras] -dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7,<10)"] - [[package]] name = "mccabe" version = "0.7.0" @@ -3670,29 +3507,16 @@ cli = ["python-dotenv (>=1.0.0)", "typer (>=0.16.0)"] rich = ["rich (>=13.9.4)"] ws = ["websockets (>=15.0.1)"] -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mistralai" -version = "2.6.0" +version = "2.7.2" description = "Python Client SDK for the Mistral AI API." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "mistralai-2.6.0-py3-none-any.whl", hash = "sha256:4b7202b0cab84ea03f8ff4a933c8cfc615b3057520d9d76467c07aa23939dab2"}, - {file = "mistralai-2.6.0.tar.gz", hash = "sha256:531a86292ad498fc0fcd6dfcd480a3f4db9e92f558e14ffb22172824831e3a6e"}, + {file = "mistralai-2.7.2-py3-none-any.whl", hash = "sha256:b2b26fa43670d6247ea352c23fe821605ecbbcf305755b629e804dbd3f6fd439"}, + {file = "mistralai-2.7.2.tar.gz", hash = "sha256:184b7a502e69cc7cdeb0b095020d7e9407a9544c4496f0317984263dac3d7939"}, ] [package.dependencies] @@ -3712,8 +3536,8 @@ realtime = ["websockets (>=13.0)"] telemetry = ["opentelemetry-exporter-otlp-proto-http (>=1.33.1,<2.0.0)", "opentelemetry-sdk (>=1.33.1,<2.0.0)"] workflow-payload-compression = ["msgpack (>=1.1.0,<2.0.0)", "zstandard (>=0.25.0,<0.26)"] workflow-payload-encryption = ["cryptography (>=41.0.0,<47.0.0)"] -workflow-payload-offloading = ["aioboto3 (>=12.4.0,<13.0.0)", "azure-identity[aio] (>=1.25.0,<2.0.0)", "azure-storage-blob[aio] (>=12.28.0,<13.0.0)", "gcloud-aio-storage (>=9.3.0,<10.0.0)"] -workflow-payload-offloading-azure = ["azure-identity[aio] (>=1.25.0,<2.0.0)", "azure-storage-blob[aio] (>=12.28.0,<13.0.0)"] +workflow-payload-offloading = ["aioboto3 (>=12.4.0,<13.0.0)", "azure-identity (>=1.25.0,<2.0.0)", "azure-storage-blob[aio] (>=12.28.0,<13.0.0)", "gcloud-aio-storage (>=9.3.0,<10.0.0)"] +workflow-payload-offloading-azure = ["azure-identity (>=1.25.0,<2.0.0)", "azure-storage-blob[aio] (>=12.28.0,<13.0.0)"] workflow-payload-offloading-gcs = ["gcloud-aio-storage (>=9.3.0,<10.0.0)"] workflow-payload-offloading-s3 = ["aioboto3 (>=12.4.0,<13.0.0)"] @@ -3952,61 +3776,62 @@ files = [ [[package]] name = "mypy" -version = "2.1.0" +version = "2.3.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc"}, - {file = "mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849"}, - {file = "mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd"}, - {file = "mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166"}, - {file = "mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8"}, - {file = "mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8"}, - {file = "mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e"}, - {file = "mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41"}, - {file = "mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca"}, - {file = "mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538"}, - {file = "mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398"}, - {file = "mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563"}, - {file = "mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389"}, - {file = "mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666"}, - {file = "mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af"}, - {file = "mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6"}, - {file = "mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211"}, - {file = "mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b"}, - {file = "mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22"}, - {file = "mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b"}, - {file = "mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8"}, - {file = "mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5"}, - {file = "mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e"}, - {file = "mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e"}, - {file = "mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285"}, - {file = "mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5"}, - {file = "mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65"}, - {file = "mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d"}, - {file = "mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2"}, - {file = "mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f"}, - {file = "mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4"}, - {file = "mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef"}, - {file = "mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135"}, - {file = "mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21"}, - {file = "mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57"}, - {file = "mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e"}, - {file = "mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780"}, - {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd"}, - {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08"}, - {file = "mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081"}, - {file = "mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7"}, - {file = "mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6"}, - {file = "mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289"}, - {file = "mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633"}, -] - -[package.dependencies] -ast-serialize = ">=0.3.0,<1.0.0" -librt = {version = ">=0.11.0", markers = "platform_python_implementation != \"PyPy\""} + {file = "mypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc"}, + {file = "mypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3"}, + {file = "mypy-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805"}, + {file = "mypy-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117"}, + {file = "mypy-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5"}, + {file = "mypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494"}, + {file = "mypy-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee"}, + {file = "mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329"}, + {file = "mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f"}, + {file = "mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a"}, + {file = "mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36"}, + {file = "mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461"}, + {file = "mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd"}, + {file = "mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568"}, + {file = "mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5"}, + {file = "mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c"}, + {file = "mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491"}, + {file = "mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7"}, + {file = "mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3"}, + {file = "mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c"}, + {file = "mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595"}, + {file = "mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97"}, + {file = "mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac"}, + {file = "mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6"}, + {file = "mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b"}, + {file = "mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2"}, + {file = "mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757"}, + {file = "mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1"}, + {file = "mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db"}, + {file = "mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762"}, + {file = "mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef"}, + {file = "mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b"}, + {file = "mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff"}, + {file = "mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4"}, + {file = "mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f"}, + {file = "mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7"}, + {file = "mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373"}, + {file = "mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556"}, + {file = "mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88"}, + {file = "mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe"}, + {file = "mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60"}, + {file = "mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654"}, + {file = "mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5"}, + {file = "mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88"}, + {file = "mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e"}, +] + +[package.dependencies] +ast-serialize = ">=0.6.0,<1.0.0" +librt = {version = ">=0.13.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=1.0.0" typing_extensions = [ @@ -4099,97 +3924,69 @@ sqlalchemy = ["sqlalchemy (>=2.0)"] [[package]] name = "numpy" -version = "2.4.4" +version = "2.5.1" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.11" +python-versions = ">=3.12" groups = ["main", "dev"] files = [ - {file = "numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40"}, - {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d"}, - {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502"}, - {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd"}, - {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5"}, - {file = "numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e"}, - {file = "numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e"}, - {file = "numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8"}, - {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121"}, - {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e"}, - {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44"}, - {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d"}, - {file = "numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827"}, - {file = "numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a"}, - {file = "numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c"}, - {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103"}, - {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83"}, - {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed"}, - {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959"}, - {file = "numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed"}, - {file = "numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf"}, - {file = "numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93"}, - {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e"}, - {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40"}, - {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e"}, - {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392"}, - {file = "numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008"}, - {file = "numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8"}, - {file = "numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b"}, - {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a"}, - {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"}, - {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252"}, - {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f"}, - {file = "numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc"}, - {file = "numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74"}, - {file = "numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d"}, - {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d"}, - {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f"}, - {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0"}, - {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150"}, - {file = "numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871"}, - {file = "numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e"}, - {file = "numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119"}, - {file = "numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0"}, + {file = "numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277"}, + {file = "numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1"}, + {file = "numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0"}, + {file = "numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e"}, + {file = "numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75"}, + {file = "numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca"}, + {file = "numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3"}, + {file = "numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9"}, + {file = "numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2"}, + {file = "numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2"}, + {file = "numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b"}, + {file = "numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1"}, + {file = "numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6"}, + {file = "numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d"}, + {file = "numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1"}, + {file = "numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd"}, + {file = "numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a"}, + {file = "numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7"}, + {file = "numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6"}, + {file = "numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9"}, + {file = "numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74"}, + {file = "numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107"}, + {file = "numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8"}, + {file = "numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75"}, + {file = "numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2"}, + {file = "numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b"}, + {file = "numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95"}, + {file = "numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21"}, + {file = "numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373"}, + {file = "numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438"}, + {file = "numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace"}, + {file = "numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a"}, + {file = "numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0"}, + {file = "numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22"}, + {file = "numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7"}, + {file = "numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d"}, + {file = "numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09"}, + {file = "numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4"}, + {file = "numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1"}, + {file = "numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077"}, + {file = "numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf"}, + {file = "numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af"}, + {file = "numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb"}, + {file = "numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3"}, ] [[package]] name = "openai" -version = "2.24.0" +version = "2.48.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94"}, - {file = "openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673"}, + {file = "openai-2.48.0-py3-none-any.whl", hash = "sha256:c98df30aaaf93c51979f64d3e7c5b76464f8be0173368266229eb8fe6bd30f2c"}, + {file = "openai-2.48.0.tar.gz", hash = "sha256:231b1e7661dda14574986c2f71451e9d584b7fe69e0ee6480e12ed090b48fc16"}, ] [package.dependencies] @@ -4200,11 +3997,13 @@ jiter = ">=0.10.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" tqdm = ">4" -typing-extensions = ">=4.11,<5" +typing-extensions = ">=4.14,<5" [package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] +aiohttp = ["aiohttp (>=3.14.1) ; python_version >= \"3.10\"", "httpx-aiohttp (>=0.1.9) ; python_version >= \"3.10\""] +bedrock = ["botocore (>=1.40.0,<1.43) ; python_version < \"3.10\"", "botocore (>=1.40.0,<2) ; python_version >= \"3.10\""] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +httpx2 = ["anyio (>=4.10.0,<5) ; python_version >= \"3.10\"", "httpx (>=0.25.1,<1) ; python_version >= \"3.10\"", "httpx2 (>=2.7.0,<3) ; python_version >= \"3.10\""] realtime = ["websockets (>=13,<16)"] voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] @@ -4284,60 +4083,54 @@ files = [ [[package]] name = "pandas" -version = "3.0.2" +version = "3.0.5" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "pandas-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0"}, - {file = "pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c"}, - {file = "pandas-3.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb"}, - {file = "pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76"}, - {file = "pandas-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e"}, - {file = "pandas-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa"}, - {file = "pandas-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df"}, - {file = "pandas-3.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f"}, - {file = "pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18"}, - {file = "pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14"}, - {file = "pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d"}, - {file = "pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f"}, - {file = "pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab"}, - {file = "pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d"}, - {file = "pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4"}, - {file = "pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd"}, - {file = "pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3"}, - {file = "pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668"}, - {file = "pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9"}, - {file = "pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e"}, - {file = "pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d"}, - {file = "pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39"}, - {file = "pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991"}, - {file = "pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84"}, - {file = "pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235"}, - {file = "pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d"}, - {file = "pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7"}, - {file = "pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677"}, - {file = "pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172"}, - {file = "pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1"}, - {file = "pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0"}, - {file = "pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b"}, - {file = "pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288"}, - {file = "pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c"}, - {file = "pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535"}, - {file = "pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db"}, - {file = "pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53"}, - {file = "pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf"}, - {file = "pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb"}, - {file = "pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d"}, - {file = "pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8"}, - {file = "pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd"}, - {file = "pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d"}, - {file = "pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660"}, - {file = "pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702"}, - {file = "pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276"}, - {file = "pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482"}, - {file = "pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043"}, + {file = "pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282"}, + {file = "pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298"}, + {file = "pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899"}, + {file = "pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a"}, + {file = "pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928"}, + {file = "pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d"}, + {file = "pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a"}, + {file = "pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a"}, + {file = "pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d"}, + {file = "pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc"}, + {file = "pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc"}, + {file = "pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34"}, + {file = "pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6"}, + {file = "pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7"}, + {file = "pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce"}, + {file = "pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41"}, + {file = "pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49"}, + {file = "pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b"}, + {file = "pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3"}, + {file = "pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b"}, + {file = "pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be"}, + {file = "pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58"}, + {file = "pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee"}, + {file = "pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6"}, + {file = "pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e"}, + {file = "pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36"}, + {file = "pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c"}, + {file = "pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0"}, + {file = "pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b"}, + {file = "pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94"}, + {file = "pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da"}, + {file = "pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92"}, + {file = "pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85"}, + {file = "pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca"}, + {file = "pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da"}, + {file = "pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a"}, + {file = "pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040"}, + {file = "pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd"}, + {file = "pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c"}, + {file = "pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea"}, + {file = "pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c"}, + {file = "pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712"}, ] [package.dependencies] @@ -4349,7 +4142,7 @@ python-dateutil = ">=2.8.2" tzdata = {version = "*", markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\""} [package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "adbc-driver-sqlite (>=1.2.0)", "beautifulsoup4 (>=4.12.3)", "bottleneck (>=1.4.2)", "fastparquet (>=2024.11.0)", "fsspec (>=2024.10.0)", "gcsfs (>=2024.10.0)", "html5lib (>=1.1)", "hypothesis (>=6.116.0)", "jinja2 (>=3.1.5)", "lxml (>=5.3.0)", "matplotlib (>=3.9.3)", "numba (>=0.60.0)", "numexpr (>=2.10.2)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.5)", "psycopg2 (>=2.9.10)", "pyarrow (>=13.0.0)", "pyiceberg (>=0.8.1)", "pymysql (>=1.1.1)", "pyreadstat (>=1.2.8)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)", "python-calamine (>=0.3.0)", "pytz (>=2024.2)", "pyxlsb (>=1.0.10)", "qtpy (>=2.4.2)", "s3fs (>=2024.10.0)", "scipy (>=1.14.1)", "tables (>=3.10.1)", "tabulate (>=0.9.0)", "xarray (>=2024.10.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.2.0)", "zstandard (>=0.23.0)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "adbc-driver-sqlite (>=1.2.0)", "beautifulsoup4 (>=4.12.3)", "bottleneck (>=1.4.2)", "fastparquet (>=2024.11.0)", "fsspec (>=2024.10.0)", "gcsfs (>=2024.10.0)", "html5lib (>=1.1)", "hypothesis (>=6.116.0)", "jinja2 (>=3.1.5)", "lxml (>=5.3.0)", "matplotlib (>=3.9.3)", "numba (>=0.60.0)", "numexpr (>=2.10.2)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.5)", "psycopg2 (>=2.9.10)", "pyarrow (>=13.0.0)", "pyiceberg (>=0.8.1)", "pymysql (>=1.1.1)", "pyreadstat (>=1.2.8)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)", "python-calamine (>=0.3.0)", "pytz (>=2020.1)", "pyxlsb (>=1.0.10)", "qtpy (>=2.4.2)", "s3fs (>=2024.10.0)", "scipy (>=1.14.1)", "tables (>=3.10.1)", "tabulate (>=0.9.0)", "xarray (>=2024.10.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.2.0)", "zstandard (>=0.23.0)"] aws = ["s3fs (>=2024.10.0)"] clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.4.2)"] compression = ["zstandard (>=0.23.0)"] @@ -4370,8 +4163,8 @@ postgresql = ["SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "psyc pyarrow = ["pyarrow (>=13.0.0)"] spss = ["pyreadstat (>=1.2.8)"] sql-other = ["SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "adbc-driver-sqlite (>=1.2.0)"] -test = ["hypothesis (>=6.116.0)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)"] -timezone = ["pytz (>=2024.2)"] +test = ["hypothesis (>=6.116.0)", "pytest (>=8.3.4,<9.1)", "pytest-xdist (>=3.6.1)"] +timezone = ["pytz (>=2020.1)"] xml = ["lxml (>=5.3.0)"] [[package]] @@ -4423,14 +4216,14 @@ pillow = "*" [[package]] name = "pdfminer-six" -version = "20251230" +version = "20260107" description = "PDF parser and analyzer" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pdfminer_six-20251230-py3-none-any.whl", hash = "sha256:9ff2e3466a7dfc6de6fd779478850b6b7c2d9e9405aa2a5869376a822771f485"}, - {file = "pdfminer_six-20251230.tar.gz", hash = "sha256:e8f68a14c57e00c2d7276d26519ea64be1b48f91db1cdc776faa80528ca06c1e"}, + {file = "pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9"}, + {file = "pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602"}, ] [package.dependencies] @@ -4442,120 +4235,116 @@ image = ["Pillow"] [[package]] name = "pdfplumber" -version = "0.11.9" +version = "0.11.10" description = "Plumb a PDF for detailed information about each char, rectangle, and line." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pdfplumber-0.11.9-py3-none-any.whl", hash = "sha256:33ec5580959ba524e9100138746e090879504c42955df1b8a997604dd326c443"}, - {file = "pdfplumber-0.11.9.tar.gz", hash = "sha256:481224b678b2bbdbf376e2c39bf914144eef7c3d301b4a28eebf0f7f6109d6dc"}, + {file = "pdfplumber-0.11.10-py3-none-any.whl", hash = "sha256:7741ea81bf165b474b153e6789d10d18e06b6ddcf3ec84289c3ef2fed6802580"}, + {file = "pdfplumber-0.11.10.tar.gz", hash = "sha256:b95b2d28c66efb0a794a83b88c6c6aea5987532a445d20a1cbcfa657022e6e57"}, ] [package.dependencies] -"pdfminer.six" = "20251230" -Pillow = ">=9.1" -pypdfium2 = ">=4.18.0" +"pdfminer.six" = "20260107" +Pillow = ">=12.2.0" +pypdfium2 = ">=5.9.0" [[package]] name = "pillow" -version = "12.2.0" +version = "12.3.0" description = "Python Imaging Library (fork)" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"}, - {file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"}, - {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"}, - {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"}, - {file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"}, - {file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"}, - {file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"}, - {file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"}, - {file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"}, - {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"}, - {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"}, - {file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"}, - {file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"}, - {file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"}, - {file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"}, - {file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"}, - {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"}, - {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"}, - {file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"}, - {file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"}, - {file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"}, - {file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"}, - {file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"}, - {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"}, - {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"}, - {file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"}, - {file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"}, - {file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"}, - {file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"}, - {file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"}, - {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"}, - {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"}, - {file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"}, - {file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"}, - {file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"}, - {file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"}, - {file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"}, - {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"}, - {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"}, - {file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"}, - {file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"}, - {file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"}, - {file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"}, - {file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"}, - {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"}, - {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"}, - {file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"}, - {file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"}, - {file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"}, - {file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"}, + {file = "pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a"}, + {file = "pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7"}, + {file = "pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f"}, + {file = "pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec"}, + {file = "pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468"}, + {file = "pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed"}, + {file = "pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1"}, + {file = "pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb"}, + {file = "pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f"}, + {file = "pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756"}, + {file = "pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6"}, + {file = "pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd"}, + {file = "pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd"}, + {file = "pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c"}, + {file = "pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5"}, + {file = "pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b"}, + {file = "pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a"}, + {file = "pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26"}, + {file = "pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965"}, + {file = "pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7"}, + {file = "pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9"}, + {file = "pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91"}, + {file = "pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c"}, + {file = "pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df"}, + {file = "pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f"}, + {file = "pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09"}, + {file = "pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec"}, + {file = "pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66"}, + {file = "pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35"}, + {file = "pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65"}, + {file = "pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3"}, + {file = "pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a"}, + {file = "pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e"}, + {file = "pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f"}, + {file = "pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8"}, + {file = "pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930"}, + {file = "pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8"}, + {file = "pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0"}, + {file = "pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321"}, + {file = "pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b"}, + {file = "pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198"}, + {file = "pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130"}, + {file = "pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a"}, + {file = "pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d"}, + {file = "pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838"}, + {file = "pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e"}, + {file = "pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17"}, + {file = "pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385"}, + {file = "pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c"}, + {file = "pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d"}, + {file = "pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931"}, + {file = "pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7"}, + {file = "pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c"}, + {file = "pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c"}, + {file = "pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f"}, + {file = "pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701"}, + {file = "pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace"}, + {file = "pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4"}, + {file = "pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39"}, + {file = "pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71"}, + {file = "pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827"}, + {file = "pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5"}, + {file = "pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658"}, + {file = "pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf"}, + {file = "pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64"}, + {file = "pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e"}, + {file = "pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777"}, + {file = "pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1"}, + {file = "pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9"}, + {file = "pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8"}, + {file = "pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418"}, + {file = "pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a"}, + {file = "pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce"}, ] [package.extras] @@ -4563,19 +4352,19 @@ docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybut fpx = ["olefile"] mic = ["olefile"] test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +tests = ["coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "setuptools", "trove-classifiers (>=2024.10.12)"] xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, - {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, + {file = "platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74"}, + {file = "platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0"}, ] [[package]] @@ -4594,18 +4383,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] -[[package]] -name = "ply" -version = "3.11" -description = "Python Lex & Yacc" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, - {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, -] - [[package]] name = "portalocker" version = "3.2.0" @@ -4628,14 +4405,14 @@ tests = ["coverage-conditional-plugin (>=0.9.0)", "portalocker[redis]", "pytest [[package]] name = "pre-commit" -version = "4.6.0" +version = "4.6.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b"}, - {file = "pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9"}, + {file = "pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717"}, + {file = "pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421"}, ] [package.dependencies] @@ -4647,162 +4424,161 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.53" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, - {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, + {file = "prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2"}, + {file = "prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6"}, ] [package.dependencies] -wcwidth = "*" +wcwidth = ">=0.1.4" [[package]] name = "propcache" -version = "0.4.1" +version = "0.5.2" description = "Accelerated property cache" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, - {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, - {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, - {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c"}, + {file = "propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d"}, + {file = "propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e"}, + {file = "propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274"}, + {file = "propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe"}, + {file = "propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d"}, + {file = "propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959"}, + {file = "propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b"}, + {file = "propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27"}, + {file = "propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f"}, + {file = "propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0"}, + {file = "propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82"}, + {file = "propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a"}, + {file = "propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476"}, + {file = "propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33"}, + {file = "propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a"}, + {file = "propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031"}, + {file = "propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42"}, + {file = "propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117"}, + {file = "propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d"}, + {file = "propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704"}, + {file = "propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4"}, + {file = "propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d"}, + {file = "propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757"}, + {file = "propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa"}, + {file = "propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc"}, + {file = "propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55"}, + {file = "propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568"}, + {file = "propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191"}, + {file = "propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7"}, + {file = "propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e"}, + {file = "propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825"}, + {file = "propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5"}, + {file = "propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4"}, + {file = "propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0"}, + {file = "propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c"}, + {file = "propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078"}, + {file = "propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335"}, + {file = "propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d"}, + {file = "propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2"}, + {file = "propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821"}, + {file = "propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370"}, + {file = "propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6"}, + {file = "propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe"}, + {file = "propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427"}, ] [[package]] name = "proto-plus" -version = "1.27.2" +version = "1.28.2" description = "Beautiful, Pythonic protocol buffers" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718"}, - {file = "proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24"}, + {file = "proto_plus-1.28.2-py3-none-any.whl", hash = "sha256:b874236fcac2358f601e4330bcb76cb8b89c851303ccf4078408b3d4774d1c52"}, + {file = "proto_plus-1.28.2.tar.gz", hash = "sha256:26d843eb99c1e32fdf1d20ff0faae56607f7748fe774acf9ecd5cfe6c6472501"}, ] [package.dependencies] @@ -4813,23 +4589,20 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "5.29.6" +version = "7.35.1" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1"}, - {file = "protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda"}, - {file = "protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269"}, - {file = "protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6"}, - {file = "protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9"}, - {file = "protobuf-5.29.6-cp38-cp38-win32.whl", hash = "sha256:36ade6ff88212e91aef4e687a971a11d7d24d6948a66751abc1b3238648f5d05"}, - {file = "protobuf-5.29.6-cp38-cp38-win_amd64.whl", hash = "sha256:831e2da16b6cc9d8f1654c041dd594eda43391affd3c03a91bea7f7f6da106d6"}, - {file = "protobuf-5.29.6-cp39-cp39-win32.whl", hash = "sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49"}, - {file = "protobuf-5.29.6-cp39-cp39-win_amd64.whl", hash = "sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18"}, - {file = "protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86"}, - {file = "protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723"}, + {file = "protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6"}, + {file = "protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799"}, + {file = "protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4"}, + {file = "protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4"}, + {file = "protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30"}, + {file = "protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87"}, + {file = "protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9"}, + {file = "protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a"}, ] [[package]] @@ -4925,14 +4698,14 @@ files = [ [[package]] name = "pyasn1" -version = "0.6.3" +version = "0.6.4" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde"}, - {file = "pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf"}, + {file = "pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b"}, + {file = "pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81"}, ] [[package]] @@ -5172,12 +4945,11 @@ version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] -markers = {main = "python_version < \"3.14\""} [package.extras] windows-terminal = ["colorama (>=0.4.6)"] @@ -5261,14 +5033,14 @@ pylint = ">=1.7" [[package]] name = "pyngrok" -version = "7.5.1" +version = "8.1.2" description = "A Python wrapper for ngrok" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyngrok-7.5.1-py3-none-any.whl", hash = "sha256:88f1ffb0ccc8dbc7810a29136eb9db3abb46b62436da438e277dfc1e70cf76ae"}, - {file = "pyngrok-7.5.1.tar.gz", hash = "sha256:934ec8a899ace8ec61c39120a27a8212aeb40293cad2f4ffc8a3d1a4bb6a8d6c"}, + {file = "pyngrok-8.1.2-py3-none-any.whl", hash = "sha256:849e9f55706288b00eb28f4ae8ea16b05e52c609f80ef4a88ca23b385f2f9178"}, + {file = "pyngrok-8.1.2.tar.gz", hash = "sha256:3b5383ec7dc4646ac0d046435eb58c6cd1cbc9acad70e6dee012b05dc25b070a"}, ] [package.dependencies] @@ -5316,46 +5088,46 @@ image = ["Pillow (>=8.0.0)"] [[package]] name = "pypdfium2" -version = "5.7.1" +version = "5.12.1" description = "Python bindings to PDFium" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "pypdfium2-5.7.1-py3-none-android_23_arm64_v8a.whl", hash = "sha256:8008f45e8adc4fc1ec2a51e018e01cd0692d4859bdbb28e88be221804f329468"}, - {file = "pypdfium2-5.7.1-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:892fcb5a618f5f551fffdb968ac2d64911953c3ba0f9aa628239705af68dbe15"}, - {file = "pypdfium2-5.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7431847d45dedc3c7ffede15b58ac611e996a0cdcd61318a0190d46b9980ac2b"}, - {file = "pypdfium2-5.7.1-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:548bd09c9f97565ae8ddba30bb65823cbf791b84e4cdb63ed582aec2c289dbe2"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18a15ad0918acc3ea98778394f0331b9ad2a1b7384ab3d8d8c63422ffd01ed13"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1df04564659d807fb38810d9bd1ac18419d8acbb5f87f2cb20675d7332635b18"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a146d036a6b085a406aa256548b827b63016714fd77f8e11b7f704c1175e8cc"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3397b0d705b6858c87dec1dc9c44d4c7094601a9b231097f441b64d1a7d5ff0b"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc2cdf603ac766b91b7c1b455197ec1c3471089d75f999b046edb65ed6cedd80"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b1a6a5f3320b59138e7570a3f78840540383d058ac180a9a21f924ad3bd7f83"}, - {file = "pypdfium2-5.7.1-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:91b809c40a5fc248107d13fbcf1dd2c64dbc8e572693a9b93e350bf31efda92b"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85611ef61cbc0f5e04de8f99fec0f3db3920b09f46c62afa08c9caa21a74b353"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b2764ab909f9b444d4e643be90b064c4053e6828c28bfd47639fc84526ba244d"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:fcea3cc20b7cca7d84ceee68b9c6ef7fe773fb71c145542769dc2ceb27e9698a"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:f04546bc314973397148805d44f8e660e81aa80c2a87e12afb892c11493ded6c"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:66275c8a854969bdf905abc7599e5623d62739c44604d69788ff5457082d275b"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:bbed8f32040ce3b3236a512265976017c2465ea6643a1730f008b39e0339b8ce"}, - {file = "pypdfium2-5.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c55d3df09bd0d72a1d192107dcbf80bcb2791662a3eca3b084001f947d3040d5"}, - {file = "pypdfium2-5.7.1-py3-none-win32.whl", hash = "sha256:4f6bbe1211c5883c8fc9ce11008347e5b96ec6571456d959ae289cecdb2867f0"}, - {file = "pypdfium2-5.7.1-py3-none-win_amd64.whl", hash = "sha256:fdf117af26bd310f4f176b3cf0e2e23f0f800e48dcf2bcf6c2cca0de3326f5cb"}, - {file = "pypdfium2-5.7.1-py3-none-win_arm64.whl", hash = "sha256:622821698fcc30fc560bd4eead6df9e6b846de9876b82861bed0091c09a4c27b"}, - {file = "pypdfium2-5.7.1.tar.gz", hash = "sha256:3b3b20a56048dbe3fd4bf397f9bec854c834668bc47ef6a7d9041b23bb04317b"}, + {file = "pypdfium2-5.12.1-py3-none-android_23_arm64_v8a.whl", hash = "sha256:05bab9b1ba2de7fc299ae2af25cb9c8a0543bc8bb893e879fe8c9ba8310e9ce4"}, + {file = "pypdfium2-5.12.1-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:d4ee061e566a6422b660cdddaaa799a2d1cbf2f016921bcaf24d61426d01d942"}, + {file = "pypdfium2-5.12.1-py3-none-macosx_13_0_arm64.whl", hash = "sha256:66a9ed40d70a5d728cd42148fecb9d7a0917c6161d6bb67c844093a4ed1df089"}, + {file = "pypdfium2-5.12.1-py3-none-macosx_13_0_x86_64.whl", hash = "sha256:847378a5ab41332998b2621b21bab2e96dc8c3eff36a08bce26695b964163983"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eabf028ad8e7bc7811c9acf3a72718c180569b624b844d2c6cc974609784275"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7857cfa6642ec5a09db12ff8f5cf6b6494585b5e3a605399fddc4fb862837b63"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05bfa20a08a96584253bbe38b60e13f81a037eac31c5579e607ec1480ad25dbf"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f059f7bdbdf4352eb83691071096940d769d6ae5930b8734237fdb1bd78fbc2"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10cbf41b21233ec5e20adfc170cf60edd77abead86a97dc708fff55a8a886c7"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07eeebb2784f4cd38d386b924235df43217a397442796673296bb6efbdaad1d0"}, + {file = "pypdfium2-5.12.1-py3-none-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5c3e6cbe43581af79526184643920ab03a9401a0c79f2226bea9d4d1e3d34008"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4648f0905441bcb141687ca2263bbf38a1aa056b943eef06019f91cff3e1da4a"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bdff622181fab64f32328591c9c8287cdc745c9a1f2afc26ca3feba39e3e6645"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:236dbdc88aa54f14b27937ccb2ebe3dcf08c10dbb8652f432ea982dc9af39732"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:9c8856ce7dd77a7827476c7d75afe1197d6cd505f5cb4167b6aacf661f3f8ea5"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:5f257bb40fa44ce9ba18d2c919777dbd3f16bf22548b1d68fd56c7c92f1de530"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:974082344172da76a5c3c0782eaedfe6069dbe88db77d8c671ef36b61e9b14e2"}, + {file = "pypdfium2-5.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:715ae16b34ea1d64884d58800155179ba700e9ea65a2f583b020666acd2bfb12"}, + {file = "pypdfium2-5.12.1-py3-none-win32.whl", hash = "sha256:e5358d2ce4ebc5c899aab1df9ca5d215357244e9168aa443225d3c1e649c7eac"}, + {file = "pypdfium2-5.12.1-py3-none-win_amd64.whl", hash = "sha256:9609be73a6701a68f29dffe0335f7a2e4b3ba581542ed65d35d49f761a4600ca"}, + {file = "pypdfium2-5.12.1-py3-none-win_arm64.whl", hash = "sha256:afc0b7e0c975a429abc75875209ce17b66d749f6ac5cbe8ba72470e83901e304"}, + {file = "pypdfium2-5.12.1.tar.gz", hash = "sha256:d0e0648fb2e28f50efcd1ec0a5a18ced9f4d66b2c227fae9b603f0a883b2d13f"}, ] [[package]] name = "pyproject-api" -version = "1.10.1" +version = "1.11.0" description = "API to interact with the python pyproject.toml based projects" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pyproject_api-1.10.1-py3-none-any.whl", hash = "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a"}, - {file = "pyproject_api-1.10.1.tar.gz", hash = "sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba"}, + {file = "pyproject_api-1.11.0-py3-none-any.whl", hash = "sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8"}, + {file = "pyproject_api-1.11.0.tar.gz", hash = "sha256:b8807d85a293e6c9f133e6575946fed45f1d42b22d58c780b33aa2421a799549"}, ] [package.dependencies] @@ -5376,14 +5148,14 @@ files = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, - {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, ] [package.dependencies] @@ -5444,24 +5216,20 @@ six = ">=1.5" [[package]] name = "python-discovery" -version = "1.4.2" +version = "1.5.0" description = "Python interpreter discovery" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500"}, - {file = "python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690"}, + {file = "python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98"}, + {file = "python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef"}, ] [package.dependencies] filelock = ">=3.15.4" platformdirs = ">=4.3.6,<5" -[package.extras] -docs = ["furo (>=2025.12.19)", "sphinx (>=9.1)", "sphinx-autodoc-typehints (>=3.6.3)", "sphinxcontrib-mermaid (>=2)", "sphinxcontrib-towncrier (>=0.4)", "towncrier (>=25.8)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] - [[package]] name = "python-docx" version = "1.2.0" @@ -5592,45 +5360,46 @@ dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "t [[package]] name = "pytz" -version = "2026.2" +version = "2026.3.post1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126"}, - {file = "pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a"}, + {file = "pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815"}, + {file = "pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d"}, ] [[package]] name = "pywin32" -version = "311" -description = "Python for Window Extensions" +version = "312" +description = "Python for Windows Extensions" optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["main"] markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ - {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, - {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, - {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, - {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, - {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, - {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, - {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, - {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, - {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, - {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, - {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, - {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, - {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, - {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, - {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, - {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, - {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, - {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, - {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, - {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, + {file = "pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e"}, + {file = "pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db"}, + {file = "pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd"}, + {file = "pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c"}, + {file = "pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a"}, + {file = "pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47"}, + {file = "pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b"}, + {file = "pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc"}, + {file = "pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950"}, + {file = "pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c"}, + {file = "pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9"}, + {file = "pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831"}, + {file = "pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b"}, + {file = "pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e"}, + {file = "pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa"}, + {file = "pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed"}, + {file = "pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5"}, + {file = "pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9"}, + {file = "pywin32-312-cp39-cp39-win32.whl", hash = "sha256:d620900033cc7531e50727c3c8333091df5dd3ffe6d68cdca38c03f5821408d5"}, + {file = "pywin32-312-cp39-cp39-win_amd64.whl", hash = "sha256:dc90147579a905b8635e1b0ec6514967dcb07e6e0d9c42f1477feef14cac23bb"}, + {file = "pywin32-312-cp39-cp39-win_arm64.whl", hash = "sha256:02ebca0f0242b75292e218065004310d6a477407c09fa449bfe4f6022bc0c0fc"}, ] [[package]] @@ -5814,20 +5583,20 @@ all = ["numpy"] [[package]] name = "redis" -version = "7.4.0" +version = "8.0.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec"}, - {file = "redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad"}, + {file = "redis-8.0.1-py3-none-any.whl", hash = "sha256:47daa35a058c23468d6437f17a8c76882cb316b838ef763036af99b96cedd743"}, + {file = "redis-8.0.1.tar.gz", hash = "sha256:afc5a7a2f5a084f5b1880dec548dd45be17db7e43c82a30d84f952aefb05cfb0"}, ] [package.extras] circuit-breaker = ["pybreaker (>=1.4.0)"] hiredis = ["hiredis (>=3.2.0)"] -jwt = ["pyjwt (>=2.9.0)"] +jwt = ["pyjwt (>=2.13.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"] otel = ["opentelemetry-api (>=1.39.1)", "opentelemetry-exporter-otlp-proto-http (>=1.39.1)", "opentelemetry-sdk (>=1.39.1)"] xxhash = ["xxhash (>=3.6.0,<3.7.0)"] @@ -5851,127 +5620,127 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2026.4.4" +version = "2026.7.19" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "regex-2026.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:74fa82dcc8143386c7c0392e18032009d1db715c25f4ba22d23dc2e04d02a20f"}, - {file = "regex-2026.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a85b620a388d6c9caa12189233109e236b3da3deffe4ff11b84ae84e218a274f"}, - {file = "regex-2026.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2895506ebe32cc63eeed8f80e6eae453171cfccccab35b70dc3129abec35a5b8"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6780f008ee81381c737634e75c24e5a6569cc883c4f8e37a37917ee79efcafd9"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:88e9b048345c613f253bea4645b2fe7e579782b82cac99b1daad81e29cc2ed8e"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:be061028481186ba62a0f4c5f1cc1e3d5ab8bce70c89236ebe01023883bc903b"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2228c02b368d69b724c36e96d3d1da721561fb9cc7faa373d7bf65e07d75cb5"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0540e5b733618a2f84e9cb3e812c8afa82e151ca8e19cf6c4e95c5a65198236f"}, - {file = "regex-2026.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cf9b1b2e692d4877880388934ac746c99552ce6bf40792a767fd42c8c99f136d"}, - {file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:011bb48bffc1b46553ac704c975b3348717f4e4aa7a67522b51906f99da1820c"}, - {file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8512fcdb43f1bf18582698a478b5ab73f9c1667a5b7548761329ef410cd0a760"}, - {file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:867bddc63109a0276f5a31999e4c8e0eb7bbbad7d6166e28d969a2c1afeb97f9"}, - {file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1b9a00b83f3a40e09859c78920571dcb83293c8004079653dd22ec14bbfa98c7"}, - {file = "regex-2026.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e355be718caf838aa089870259cf1776dc2a4aa980514af9d02c59544d9a8b22"}, - {file = "regex-2026.4.4-cp310-cp310-win32.whl", hash = "sha256:33bfda9684646d323414df7abe5692c61d297dbb0530b28ec66442e768813c59"}, - {file = "regex-2026.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:0709f22a56798457ae317bcce42aacee33c680068a8f14097430d9f9ba364bee"}, - {file = "regex-2026.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:ee9627de8587c1a22201cb16d0296ab92b4df5cdcb5349f4e9744d61db7c7c98"}, - {file = "regex-2026.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4c36a85b00fadb85db9d9e90144af0a980e1a3d2ef9cd0f8a5bef88054657c6"}, - {file = "regex-2026.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dcb5453ecf9cd58b562967badd1edbf092b0588a3af9e32ee3d05c985077ce87"}, - {file = "regex-2026.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6aa809ed4dc3706cc38594d67e641601bd2f36d5555b2780ff074edfcb136cf8"}, - {file = "regex-2026.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33424f5188a7db12958246a54f59a435b6cb62c5cf9c8d71f7cc49475a5fdada"}, - {file = "regex-2026.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d346fccdde28abba117cc9edc696b9518c3307fbfcb689e549d9b5979018c6d"}, - {file = "regex-2026.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:415a994b536440f5011aa77e50a4274d15da3245e876e5c7f19da349caaedd87"}, - {file = "regex-2026.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21e5eb86179b4c67b5759d452ea7c48eb135cd93308e7a260aa489ed2eb423a4"}, - {file = "regex-2026.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:312ec9dd1ae7d96abd8c5a36a552b2139931914407d26fba723f9e53c8186f86"}, - {file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0d2b28aa1354c7cd7f71b7658c4326f7facac106edd7f40eda984424229fd59"}, - {file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:349d7310eddff40429a099c08d995c6d4a4bfaf3ff40bd3b5e5cb5a5a3c7d453"}, - {file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e7ab63e9fe45a9ec3417509e18116b367e89c9ceb6219222a3396fa30b147f80"}, - {file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fe896e07a5a2462308297e515c0054e9ec2dd18dfdc9427b19900b37dfe6f40b"}, - {file = "regex-2026.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb59c65069498dbae3c0ef07bbe224e1eaa079825a437fb47a479f0af11f774f"}, - {file = "regex-2026.4.4-cp311-cp311-win32.whl", hash = "sha256:2a5d273181b560ef8397c8825f2b9d57013de744da9e8257b8467e5da8599351"}, - {file = "regex-2026.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:9542ccc1e689e752594309444081582f7be2fdb2df75acafea8a075108566735"}, - {file = "regex-2026.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:b5f9fb784824a042be3455b53d0b112655686fdb7a91f88f095f3fee1e2a2a54"}, - {file = "regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52"}, - {file = "regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb"}, - {file = "regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76"}, - {file = "regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be"}, - {file = "regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1"}, - {file = "regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13"}, - {file = "regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9"}, - {file = "regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d"}, - {file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3"}, - {file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0"}, - {file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043"}, - {file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244"}, - {file = "regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73"}, - {file = "regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f"}, - {file = "regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b"}, - {file = "regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983"}, - {file = "regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943"}, - {file = "regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031"}, - {file = "regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7"}, - {file = "regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17"}, - {file = "regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17"}, - {file = "regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae"}, - {file = "regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e"}, - {file = "regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d"}, - {file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27"}, - {file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf"}, - {file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0"}, - {file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa"}, - {file = "regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b"}, - {file = "regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62"}, - {file = "regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81"}, - {file = "regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427"}, - {file = "regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c"}, - {file = "regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141"}, - {file = "regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717"}, - {file = "regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07"}, - {file = "regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca"}, - {file = "regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520"}, - {file = "regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883"}, - {file = "regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b"}, - {file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1"}, - {file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b"}, - {file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff"}, - {file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb"}, - {file = "regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4"}, - {file = "regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa"}, - {file = "regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0"}, - {file = "regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe"}, - {file = "regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7"}, - {file = "regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752"}, - {file = "regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951"}, - {file = "regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f"}, - {file = "regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8"}, - {file = "regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4"}, - {file = "regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9"}, - {file = "regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83"}, - {file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb"}, - {file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465"}, - {file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4"}, - {file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566"}, - {file = "regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95"}, - {file = "regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8"}, - {file = "regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4"}, - {file = "regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f"}, - {file = "regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3"}, - {file = "regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e"}, - {file = "regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6"}, - {file = "regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359"}, - {file = "regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a"}, - {file = "regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55"}, - {file = "regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99"}, - {file = "regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790"}, - {file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc"}, - {file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f"}, - {file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863"}, - {file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a"}, - {file = "regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81"}, - {file = "regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74"}, - {file = "regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45"}, - {file = "regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d"}, - {file = "regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52"}, + {file = "regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6"}, + {file = "regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a"}, + {file = "regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902"}, + {file = "regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e"}, + {file = "regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db"}, + {file = "regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae"}, + {file = "regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc"}, + {file = "regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78"}, + {file = "regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327"}, + {file = "regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d"}, + {file = "regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965"}, + {file = "regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd"}, + {file = "regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68"}, + {file = "regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035"}, + {file = "regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a"}, + {file = "regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5"}, + {file = "regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312"}, + {file = "regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38"}, + {file = "regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15"}, + {file = "regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc"}, + {file = "regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2"}, + {file = "regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404"}, + {file = "regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e"}, + {file = "regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda"}, + {file = "regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a"}, + {file = "regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e"}, + {file = "regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0"}, + {file = "regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4"}, + {file = "regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974"}, + {file = "regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac"}, + {file = "regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a"}, + {file = "regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97"}, + {file = "regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4"}, + {file = "regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa"}, + {file = "regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac"}, + {file = "regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518"}, + {file = "regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276"}, + {file = "regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966"}, + {file = "regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44"}, + {file = "regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78"}, + {file = "regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2"}, + {file = "regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547"}, + {file = "regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5"}, ] [[package]] @@ -6020,206 +5789,187 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "requirements-parser" -version = "0.13.0" +version = "0.13.1" description = "This is a small Python module for parsing Pip requirement files." optional = false python-versions = "<4.0,>=3.8" groups = ["dev"] files = [ - {file = "requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14"}, - {file = "requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418"}, + {file = "requirements_parser-0.13.1-py3-none-any.whl", hash = "sha256:6e385663eb32589d16e5b22bb6e5251a57908e73803ffff438b53cd6ea2056e0"}, + {file = "requirements_parser-0.13.1.tar.gz", hash = "sha256:78811383b2089b6c5197a1431bc2c12ff950245edca39a23eea3460782038dd3"}, ] [package.dependencies] packaging = ">=23.2" -[[package]] -name = "rich" -version = "15.0.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.9.0" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb"}, - {file = "rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - [[package]] name = "rpds-py" -version = "0.30.0" +version = "2026.6.3" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["main", "dev"] files = [ - {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, - {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, - {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, - {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, - {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, - {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, - {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, - {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, - {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, + {file = "rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7"}, + {file = "rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3"}, + {file = "rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6"}, + {file = "rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127"}, + {file = "rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804"}, + {file = "rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0"}, + {file = "rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a"}, + {file = "rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc"}, + {file = "rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f"}, + {file = "rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96"}, + {file = "rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223"}, + {file = "rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885"}, + {file = "rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d"}, + {file = "rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80"}, + {file = "rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb"}, + {file = "rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e"}, + {file = "rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc"}, + {file = "rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd"}, + {file = "rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5"}, + {file = "rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e"}, + {file = "rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2"}, + {file = "rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13"}, + {file = "rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a"}, + {file = "rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107"}, + {file = "rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146"}, + {file = "rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577"}, + {file = "rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76"}, + {file = "rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826"}, + {file = "rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4"}, ] [[package]] name = "ruff" -version = "0.15.12" +version = "0.16.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c"}, - {file = "ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c"}, - {file = "ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e"}, - {file = "ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20"}, - {file = "ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d"}, - {file = "ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f"}, - {file = "ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6"}, + {file = "ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e"}, + {file = "ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522"}, + {file = "ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b"}, + {file = "ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09"}, + {file = "ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed"}, + {file = "ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb"}, + {file = "ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472"}, + {file = "ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d"}, + {file = "ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982"}, ] [[package]] name = "selenium" -version = "4.43.0" +version = "4.46.0" description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "selenium-4.43.0-py3-none-any.whl", hash = "sha256:4f97639055dcfa9eadf8ccf549ba7b0e49c655d4e2bde19b9a44e916b754e769"}, - {file = "selenium-4.43.0.tar.gz", hash = "sha256:bada5c08a989f812728a4b5bea884d8e91894e939a441cc3a025201ce718581e"}, + {file = "selenium-4.46.0-py3-none-any.whl", hash = "sha256:f03e864770a21ab32009961c9d015cb2c6275eba45c926c272e02d90af423aad"}, + {file = "selenium-4.46.0.tar.gz", hash = "sha256:54f7e1a4df5f7508ac8c38ce2ea584db1b27083dd79962b22524219219df5cbe"}, ] [package.dependencies] -certifi = ">=2026.1.4" +certifi = ">=2026.2.25" trio = ">=0.31.0,<1.0" trio-websocket = ">=0.12.2,<1.0" typing_extensions = ">=4.15.0,<5.0" @@ -6228,37 +5978,24 @@ websocket-client = ">=1.8.0,<2.0" [[package]] name = "setuptools" -version = "82.0.1" +version = "83.0.0" description = "Most extensible Python build backend with support for C/C++ extension modules" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, - {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, + {file = "setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3"}, + {file = "setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] +enabler = ["pytest-enabler (>=3.4)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] [[package]] name = "six" @@ -6298,14 +6035,14 @@ files = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.9.1" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65"}, - {file = "soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e"}, + {file = "soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c"}, + {file = "soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba"}, ] [[package]] @@ -6326,14 +6063,14 @@ doc = ["sphinx"] [[package]] name = "sse-starlette" -version = "3.4.1" +version = "3.4.6" description = "SSE plugin for Starlette" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "sse_starlette-3.4.1-py3-none-any.whl", hash = "sha256:6b43cf21f1d574d582a6e1b0cfbde1c94dc86a32a701a7168c99c4475c6bd1d0"}, - {file = "sse_starlette-3.4.1.tar.gz", hash = "sha256:f780bebcf6c8997fe514e3bd8e8c648d8284976b391c8bed0bcb1f611632b555"}, + {file = "sse_starlette-3.4.6-py3-none-any.whl", hash = "sha256:56217ab4c9a9f9c5db7b21e08732d3e7c2b807f45231ad23de0551a24c4a41f6"}, + {file = "sse_starlette-3.4.6.tar.gz", hash = "sha256:725f8a1bd6d26ae1b2c9610c0ef5065dfdd496f3988d28adcf8c4b49dc25c627"}, ] [package.dependencies] @@ -6368,20 +6105,19 @@ full = ["httpx (>=0.27.0,<0.29.0)", "httpx2 (>=2.0.0)", "itsdangerous", "jinja2" [[package]] name = "stone" -version = "3.3.1" +version = "3.5.3" description = "Stone is an interface description language (IDL) for APIs." optional = false -python-versions = "*" +python-versions = ">=3.11" groups = ["main"] files = [ - {file = "stone-3.3.1-py2-none-any.whl", hash = "sha256:cd2f7f9056fc39b16c8fd46a26971dc5ccd30b5c2c246566cd2c0dd27ff96609"}, - {file = "stone-3.3.1-py3-none-any.whl", hash = "sha256:e15866fad249c11a963cce3bdbed37758f2e88c8ff4898616bc0caeb1e216047"}, - {file = "stone-3.3.1.tar.gz", hash = "sha256:4ef0397512f609757975f7ec09b35639d72ba7e3e17ce4ddf399578346b4cb50"}, + {file = "stone-3.5.3-py3-none-any.whl", hash = "sha256:5d78bd0b60692756b44758d876d74e4ed2ed938e29aa62646e3cbc247844027b"}, + {file = "stone-3.5.3.tar.gz", hash = "sha256:d0d99f14d154452e71b3e2752efdbbb1769e4fecbc1291b1dfd1a046f7b6a1cb"}, ] [package.dependencies] -ply = ">=3.4" -six = ">=1.12.0" +Jinja2 = ">=3.0.3" +packaging = ">=21.0" [[package]] name = "tabulate" @@ -6431,112 +6167,105 @@ rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "tiktoken" -version = "0.12.0" +version = "0.13.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, - {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, - {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, - {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, - {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, - {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, - {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, - {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, - {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, - {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, -] - -[package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" + {file = "tiktoken-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4"}, + {file = "tiktoken-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9"}, + {file = "tiktoken-0.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:9b842981fa91accdffd48ff6408a977b7a91c3fbda55d353c3c68114d5c9d69e"}, + {file = "tiktoken-0.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed5a30027cb4d8c7ca8b273d4766f3db3cf58fad9e9f3b1a68a351ffb54873d5"}, + {file = "tiktoken-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7ab10f4a21c2999846940113f6dbd72e0fa06a24119feddd74cc47e85818e06d"}, + {file = "tiktoken-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a2937ad042d49d50eac6e1ba07c5661d4bd3942a5b1e0c0d08475c4df83676e1"}, + {file = "tiktoken-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:44733b99bfd72b590cd0936b1c01b3b4dd73122db2d544bc1ceeb18a7678c910"}, + {file = "tiktoken-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb"}, + {file = "tiktoken-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26"}, + {file = "tiktoken-0.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4"}, + {file = "tiktoken-0.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173"}, + {file = "tiktoken-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff"}, + {file = "tiktoken-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed"}, + {file = "tiktoken-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94"}, + {file = "tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791"}, + {file = "tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b"}, + {file = "tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7"}, + {file = "tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649"}, + {file = "tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b"}, + {file = "tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91"}, + {file = "tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41"}, + {file = "tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154"}, + {file = "tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545"}, + {file = "tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2"}, + {file = "tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf"}, + {file = "tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486"}, + {file = "tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615"}, + {file = "tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7"}, + {file = "tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67"}, + {file = "tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a"}, + {file = "tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d"}, + {file = "tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce"}, + {file = "tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2"}, + {file = "tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f"}, + {file = "tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec"}, + {file = "tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471"}, + {file = "tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd"}, + {file = "tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881"}, + {file = "tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24"}, + {file = "tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273"}, + {file = "tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51"}, + {file = "tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58"}, + {file = "tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b"}, + {file = "tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448"}, + {file = "tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a"}, + {file = "tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad"}, + {file = "tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e"}, + {file = "tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424"}, + {file = "tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07"}, + {file = "tiktoken-0.13.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:35e1ea1e0631c04f551297284a1ab7e1f65a3c55a9a48728d5e0f66b4527c04a"}, + {file = "tiktoken-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a3b536c55802fe42f4b4644d2be4f04bf788506b48de0a0a658cb58f8bce232"}, + {file = "tiktoken-0.13.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b8ac2d6420ff05841a89ba5205c6d45f56c4f6843454f3c884b7eb1a2a8dddb2"}, + {file = "tiktoken-0.13.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:477c9a38e20d0ed248090509acf1e839ad3967a4f00b4b0f958210049f656dee"}, + {file = "tiktoken-0.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da86f8c96ac1c235d7a3b3eebff1eacfdbcfb8ad792706943268d4d2938fbafe"}, + {file = "tiktoken-0.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9b8858b29804b3a0add25ce9e62fb00f89f621dc754d75d03ca419d17e8ddf67"}, + {file = "tiktoken-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:b967dfb9d0adf9a631953b1b40717684f04478270fc51bbccdd2f838d67a2f00"}, + {file = "tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1"}, +] + +[package.dependencies] +regex = "*" +requests = "*" [package.extras] -blobfile = ["blobfile (>=2)"] +blobfile = ["blobfile (>=3)"] [[package]] name = "tokenizers" -version = "0.22.2" +version = "0.23.1" description = "" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c"}, - {file = "tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b"}, - {file = "tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a"}, - {file = "tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5"}, - {file = "tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92"}, - {file = "tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48"}, - {file = "tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195"}, - {file = "tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319f659ee992222f04e58f84cbf407cfa66a65fe3a8de44e8ad2bc53e7d99012"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e50f8554d504f617d9e9d6e4c2c2884a12b388a97c5c77f0bc6cf4cd032feee"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a62ba2c5faa2dd175aaeed7b15abf18d20266189fb3406c5d0550dd34dd5f37"}, - {file = "tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143b999bdc46d10febb15cbffb4207ddd1f410e2c755857b5a0797961bbdc113"}, - {file = "tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917"}, + {file = "tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224"}, + {file = "tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4"}, + {file = "tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4"}, + {file = "tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96"}, + {file = "tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948"}, + {file = "tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7"}, + {file = "tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9"}, + {file = "tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa"}, ] [package.dependencies] @@ -6631,26 +6360,26 @@ files = [ [[package]] name = "tomlkit" -version = "0.15.0" +version = "0.15.1" description = "Style preserving TOML library" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738"}, - {file = "tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3"}, + {file = "tomlkit-0.15.1-py3-none-any.whl", hash = "sha256:177a05aece5a8ca5266fd3c448abb47b8d352f09d477d3ca8332db4d89b24304"}, + {file = "tomlkit-0.15.1.tar.gz", hash = "sha256:e25bbf38843005246210a12982776f27f99cb9be67160e14434d0c0d21ee1e97"}, ] [[package]] name = "tox" -version = "4.53.1" +version = "4.58.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "tox-4.53.1-py3-none-any.whl", hash = "sha256:4a9948607e976a337c22d64a1b4fafd486125e82f00ab6ce32fa6cacc23f48b1"}, - {file = "tox-4.53.1.tar.gz", hash = "sha256:7be9805ed4a34242510c7acc9a7e3a01a35942e08f31f8bd69067c3a37130afc"}, + {file = "tox-4.58.0-py3-none-any.whl", hash = "sha256:dcae21f5f015f3a67658e35644cce0d1aa0dedcd06f3927f95d84e1717f6cea5"}, + {file = "tox-4.58.0.tar.gz", hash = "sha256:ab0b126a04dd56bc18e6d216386db09335247f2289b54cf534deb5c4ae3a8d2e"}, ] [package.dependencies] @@ -6661,34 +6390,35 @@ packaging = ">=26" platformdirs = ">=4.9.4" pluggy = ">=1.6" pyproject-api = ">=1.10" -python-discovery = ">=1.2.2" +python-discovery = ">=1.4.4" tomli-w = ">=1.2" virtualenv = ">=21.1" [package.extras] completion = ["argcomplete (>=3.6.3)"] +testing = ["devpi-process (>=1.1.1)", "pytest (>=9.0.2)", "pytest-mock (>=3.15.1)"] [[package]] name = "tqdm" -version = "4.67.3" +version = "4.70.0" description = "Fast, Extensible Progress Meter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] +markers = "python_version < \"3.14\"" files = [ - {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, - {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, + {file = "tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953"}, + {file = "tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] +discord = ["envwrap", "requests"] notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] +slack = ["envwrap", "slack-sdk"] +telegram = ["envwrap", "requests"] [[package]] name = "trio" @@ -6727,35 +6457,16 @@ outcome = ">=1.2.0" trio = ">=0.11" wsproto = ">=0.14" -[[package]] -name = "typer" -version = "0.23.1" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.14\"" -files = [ - {file = "typer-0.23.1-py3-none-any.whl", hash = "sha256:3291ad0d3c701cbf522012faccfbb29352ff16ad262db2139e6b01f15781f14e"}, - {file = "typer-0.23.1.tar.gz", hash = "sha256:2070374e4d31c83e7b61362fd859aa683576432fd5b026b060ad6b4cd3b86134"}, -] - -[package.dependencies] -annotated-doc = ">=0.0.2" -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" - [[package]] name = "types-httplib2" -version = "0.31.2.20260408" +version = "0.32.0.20260720" description = "Typing stubs for httplib2" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_httplib2-0.31.2.20260408-py3-none-any.whl", hash = "sha256:a6ef6deea469e71fbde4226c2a79c5ed8f40f201171d9bcd2ba20752dbf3766e"}, - {file = "types_httplib2-0.31.2.20260408.tar.gz", hash = "sha256:ebf7ba1b5910500762df8bbafcad8f6e5813403442dc0439d86788d81327f1fe"}, + {file = "types_httplib2-0.32.0.20260720-py3-none-any.whl", hash = "sha256:c8e838557fccd39bb08569839c7e625489c5b2db1c89ab54b6a3b808b9e9b392"}, + {file = "types_httplib2-0.32.0.20260720.tar.gz", hash = "sha256:fdf42301174bccd6cede13dcffe130753f5c32c64b0a2b681a011504ffe6d1eb"}, ] [[package]] @@ -6775,62 +6486,62 @@ referencing = "*" [[package]] name = "types-python-dateutil" -version = "2.9.0.20260408" +version = "2.9.0.20260716" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_python_dateutil-2.9.0.20260408-py3-none-any.whl", hash = "sha256:473139d514a71c9d1fbd8bb328974bedcb1cc3dba57aad04ffa4157f483c216f"}, - {file = "types_python_dateutil-2.9.0.20260408.tar.gz", hash = "sha256:8b056ec01568674235f64ecbcef928972a5fac412f5aab09c516dfa2acfbb582"}, + {file = "types_python_dateutil-2.9.0.20260716-py3-none-any.whl", hash = "sha256:1ae41d51a5c5f6bbeeb7f1f34df7086d55ff1605cf88d2ed71a7a276e1a7794e"}, + {file = "types_python_dateutil-2.9.0.20260716.tar.gz", hash = "sha256:1d55d1c3024bdb4861bb6a6622c9ec800c433d87bdc5b16fb84cdd0eed4ef2cb"}, ] [[package]] name = "types-pytz" -version = "2026.1.1.20260408" +version = "2026.3.1.20260727" description = "Typing stubs for pytz" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_pytz-2026.1.1.20260408-py3-none-any.whl", hash = "sha256:c7e4dec76221fb7d0c97b91ad8561d689bebe39b6bcb7b728387e7ffd8cde788"}, - {file = "types_pytz-2026.1.1.20260408.tar.gz", hash = "sha256:89b6a34b9198ea2a4b98a9d15cbca987053f52a105fd44f7ce3789cae4349408"}, + {file = "types_pytz-2026.3.1.20260727-py3-none-any.whl", hash = "sha256:42ac44e83645bfceb46597342c8fb8ec028a1407e2feae9c5c539986eab6b57a"}, + {file = "types_pytz-2026.3.1.20260727.tar.gz", hash = "sha256:4364075b6867dd15b210bb8c1d29727d609917129b45600defe1d4b3eda5ecb9"}, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20260408" +version = "6.0.12.20260724" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "types_pyyaml-6.0.12.20260408-py3-none-any.whl", hash = "sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384"}, - {file = "types_pyyaml-6.0.12.20260408.tar.gz", hash = "sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307"}, + {file = "types_pyyaml-6.0.12.20260724-py3-none-any.whl", hash = "sha256:d57db930a4b2efbc57cf430ec8882765d246929432fa253092f383902329a453"}, + {file = "types_pyyaml-6.0.12.20260724.tar.gz", hash = "sha256:3c1ce1bb73cd5ec02e90390c2b1f00e810d241d8825fd73ff359696839271b6b"}, ] [[package]] name = "types-reportlab" -version = "4.5.1.20260521" +version = "4.5.1.20260724" description = "Typing stubs for reportlab" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_reportlab-4.5.1.20260521-py3-none-any.whl", hash = "sha256:b438c76655dbf24e4b4de157611ff137e1fd670e942d039f8a9321b3fe65cba1"}, - {file = "types_reportlab-4.5.1.20260521.tar.gz", hash = "sha256:072d6d700d42f37bd515a1f92a4298066909d5ada3d40a9f7fd61f414f2691af"}, + {file = "types_reportlab-4.5.1.20260724-py3-none-any.whl", hash = "sha256:182b52f41e346587c97e0d8cd677c5c64a821342497cb1ab4478e2bb19d7d8e4"}, + {file = "types_reportlab-4.5.1.20260724.tar.gz", hash = "sha256:71d3c85fa2055e34803c3a21410db39d4f039b3a6e0db39f9c3a43b202d5997c"}, ] [[package]] name = "types-requests" -version = "2.33.0.20260503" +version = "2.33.0.20260712" description = "Typing stubs for requests" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_requests-2.33.0.20260503-py3-none-any.whl", hash = "sha256:02aaa7e3577a13471715bb1bddb693cc985ea514f754b503bf033e6a09a3e528"}, - {file = "types_requests-2.33.0.20260503.tar.gz", hash = "sha256:9721b2d9dbee7131f2fb39f20f0ebb1999c18cef4b512c9a7932f3722de7c5f4"}, + {file = "types_requests-2.33.0.20260712-py3-none-any.whl", hash = "sha256:de027e28c171d3da529689cbfa023b0b4eab188c8dfa22fd834eebd2cee6e7bb"}, + {file = "types_requests-2.33.0.20260712.tar.gz", hash = "sha256:2141b67ab534a5c5cd2dac5034f2a35f42e699c5bf185eee608c5246a069d7fb"}, ] [package.dependencies] @@ -6838,14 +6549,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] @@ -6865,34 +6576,35 @@ typing-extensions = ">=4.12.0" [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main", "dev"] files = [ - {file = "tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7"}, - {file = "tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10"}, + {file = "tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931"}, + {file = "tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415"}, ] markers = {dev = "sys_platform == \"win32\""} [[package]] name = "tzlocal" -version = "5.3.1" +version = "5.4.4" description = "tzinfo object for the local timezone" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"}, - {file = "tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd"}, + {file = "tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15"}, + {file = "tzlocal-5.4.4.tar.gz", hash = "sha256:8dbb8660838688a7b6ba4fed31d18dedf842afb4d47ca050d6d891c2c15f3be4"}, ] [package.dependencies] tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +devenv = ["zest.releaser"] +testing = ["check_manifest", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "ruff"] [[package]] name = "uritemplate" @@ -6929,15 +6641,15 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "uvicorn" -version = "0.46.0" +version = "0.51.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.10" groups = ["main"] markers = "sys_platform != \"emscripten\"" files = [ - {file = "uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048"}, - {file = "uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d"}, + {file = "uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b"}, + {file = "uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0"}, ] [package.dependencies] @@ -6945,7 +6657,7 @@ click = ">=7.0" h11 = ">=0.8" [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.20)", "websockets (>=10.4)"] +standard = ["httptools (>=0.8.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.20)", "websockets (>=13.0)"] [[package]] name = "vine" @@ -6961,14 +6673,14 @@ files = [ [[package]] name = "virtualenv" -version = "21.5.1" +version = "21.7.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783"}, - {file = "virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8"}, + {file = "virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd"}, + {file = "virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c"}, ] [package.dependencies] @@ -6991,14 +6703,14 @@ files = [ [[package]] name = "wcwidth" -version = "0.7.0" +version = "0.8.2" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2"}, - {file = "wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0"}, + {file = "wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85"}, + {file = "wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda"}, ] [[package]] @@ -7020,73 +6732,121 @@ test = ["pytest", "websockets"] [[package]] name = "websockets" -version = "16.0" +version = "16.1.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a"}, - {file = "websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0"}, - {file = "websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957"}, - {file = "websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72"}, - {file = "websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde"}, - {file = "websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3"}, - {file = "websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3"}, - {file = "websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9"}, - {file = "websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35"}, - {file = "websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8"}, - {file = "websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad"}, - {file = "websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d"}, - {file = "websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe"}, - {file = "websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b"}, - {file = "websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5"}, - {file = "websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64"}, - {file = "websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6"}, - {file = "websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac"}, - {file = "websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00"}, - {file = "websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79"}, - {file = "websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39"}, - {file = "websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c"}, - {file = "websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f"}, - {file = "websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1"}, - {file = "websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2"}, - {file = "websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89"}, - {file = "websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea"}, - {file = "websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9"}, - {file = "websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230"}, - {file = "websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c"}, - {file = "websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5"}, - {file = "websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82"}, - {file = "websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8"}, - {file = "websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f"}, - {file = "websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a"}, - {file = "websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156"}, - {file = "websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0"}, - {file = "websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904"}, - {file = "websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4"}, - {file = "websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e"}, - {file = "websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4"}, - {file = "websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1"}, - {file = "websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3"}, - {file = "websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8"}, - {file = "websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d"}, - {file = "websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244"}, - {file = "websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e"}, - {file = "websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641"}, - {file = "websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8"}, - {file = "websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e"}, - {file = "websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944"}, - {file = "websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206"}, - {file = "websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6"}, - {file = "websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd"}, - {file = "websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d"}, - {file = "websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03"}, - {file = "websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da"}, - {file = "websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c"}, - {file = "websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767"}, - {file = "websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec"}, - {file = "websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731"}, + {file = "websockets-16.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9246a0d063cfcbcc85f2359dd6876d681213f4790832272aa16641b4ed5d64d4"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1214e673c404684b9bf7154f5cf43b45025b1a6160fac3a9e438e9c1a97e22cb"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90001d893bc368e302ef168d82130b4e4fdd27b85fa094682df9b667c2d48838"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:130937b167a52af203c8d58e78d67705874e82759862e3b9671a452fec4abc87"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c9f23004a3d40e89c01a7955d186a6cc83418d93b749701944ce2de3e95a1f3"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f55f0b01956a094c8587146d9558c91937e78789c333860ffaf35931a6e5dbc4"}, + {file = "websockets-16.1.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6aaface73b9c71974c6497366d8b9628357f6c9749e09c4ea3610176c63f2ae3"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc0fad4933f427acd5b1cec210f3ea6dce7089e1724e4b9ec6ef47c6c04d1b3b"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2769a0344a09e9ccf5b3cce538bc75a51b53eff3275d3896310c8552049195d"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f70541f3104339f59f830522d94ebadb1bf47426287381623443d8bb1cdbf33d"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:dc385593a42e31cd6fb60c19f0ecb015b386603818fc2c6c274fb42bd2bb4165"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:387e8e4aa5df2f90b198fa3cad3478822a89cf905b6a6d6c97dc3664689640cc"}, + {file = "websockets-16.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fd46fff7eb62c24804d234f0051c7a8ea81285ad63e0337d3dcf33ca82aee58a"}, + {file = "websockets-16.1.1-cp310-cp310-win32.whl", hash = "sha256:7883388947767080f094950b342b30d35a2a06b849cd967c422fa0db72b40ea9"}, + {file = "websockets-16.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d57685547e0060cc6fd90ee6a28405d6bd395e525545f13c8d7cd99c78afd79f"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d0fcf657e9f13ff4b177960ab2200237b12994232dfb6df16f1cfe1d4339f93c"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b852788aa51764e2d8e4cf5493d559326bcae5e38d16ba25ffa322b034df272a"}, + {file = "websockets-16.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1427fb4cf0d72f66333e2cacc3ff5f575bf2d7008166ce991a4a470b21d51a22"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:da4ca1a9d72f9030b3146b8d7022719a9f3d478f61efe6f7dd51d243f61c51b2"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86d7f0f8bdb25d2c632b72527325e4776430fd5bc61b9118de4e2b8ddb5f5b01"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dfcad78ea1492ee3a9ec765cb7f51bbc17d477107aaf6b22abf7b2558d1c5a0"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb9a0a6dc3d1b3986cb88091b6899f0396651e0f74e2c9766ab8d6ffc3842e29"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29dfa8114c4a620c69591c5973860f768eac29d3fd6904f37f34266cb219c512"}, + {file = "websockets-16.1.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff9417c0ada4d0f7d212f928303e5579bdf3ace4c802fa4afabb30995da58c3"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fe0b50da2d84535fb4f7b4bfa951280f97ce3d558a0443b541166d609e67b57"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34420aaa64440ebd51ac72ca8a45ef4626429438c9b02e633ae412ed43f925d3"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a6a61aff018180c9c50b7b0da33bfd29d378af3497429c95006c589a23a11648"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:04fd29a0e2fe9414a95b00e92c67ae51bf900c50c0f8a4b2dafdad621f49ea1d"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5c31aa7e39ee3e8a358573257f1c0bb5c52430d1b637030dd9c8cc2c282926be"}, + {file = "websockets-16.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d14bfb217eb4701e850f1525c9d29d79c44794cdf1c299ead25f39f8c78dea81"}, + {file = "websockets-16.1.1-cp311-cp311-win32.whl", hash = "sha256:2e28e602bb13da44fbe518c1781a88e3b9d4c3d48d02c9bad83e546164336f57"}, + {file = "websockets-16.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7421fad442de870a8cbf2287d1cad7e706ece0dbfeba5e911df132cbdc1cb56a"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc97814dfb786a83b6e2dc2e79351e1b83e6d715647d6887fcabd83026417a00"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e047dc87ef7ca50f4d309bf775ad4a71711c58556d75d7bd0604b2317f43e94b"}, + {file = "websockets-16.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01fbdcbac298efe19360b94bc0039c8f746f0220ba570f327577bfee81059175"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f62863e8a00a6d33c3d6566ec0b89f23787b747ffe0c3bc71ec0e76b82c94b1"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8087e82f842609734c9b5a1330464f8e94e346ba0e18c832c08bafa4b0d63c15"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2bb5d041a8307d2e18782e7ce777f6fdb1e8c2f5d09291484b18c294b789d9aa"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1db4de4a0e95673f7545d393c49eeb0c2f18ac1ef93073218c79d5cdb2ee75ab"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f17dbe07eb3ea7f99e4df9b7e0efefe80fbf30d37a8cc4d561a0aed310bc8847"}, + {file = "websockets-16.1.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b57693728576d84ede0a77987ab16881b783d2cd9f1dc180a8fbbc3f79c4428"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a636ff1e7a5c4edf71ef0e79adae7f25dba93b4fcbe3dc958733477ffeb0eaf"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d6bec75c290fe484a8ba4cacdf838501e17c06ecfbbf31eede81a9e431bd7751"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:54509b8e92fee4453e152b7558ddef37ce9705a044922f2095a6105e3f80c96f"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f0aa4aad3b1b69ad3fd85a0fd0952ec64331c762bd77ec51cc814170873890b2"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:42290eb6db4ccaca7012656738214f8514082fb6fa40cdeb61bb9a471b52e383"}, + {file = "websockets-16.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:53260c8930da5771cec89439bff99c20c8cb03ddb9588b980697355a83cd4bd3"}, + {file = "websockets-16.1.1-cp312-cp312-win32.whl", hash = "sha256:1d27fa8462ad6a1cb36206a3d0640b2333340def181fae11ed7f9adeaa5c0747"}, + {file = "websockets-16.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b436f6ec4fc3a6b4237c84d3f83170ed2b40bb584222f0ac47a0c8a5921980c7"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df"}, + {file = "websockets-16.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49"}, + {file = "websockets-16.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e"}, + {file = "websockets-16.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87"}, + {file = "websockets-16.1.1-cp313-cp313-win32.whl", hash = "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea"}, + {file = "websockets-16.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293"}, + {file = "websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3"}, + {file = "websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985"}, + {file = "websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9"}, + {file = "websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328"}, + {file = "websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999"}, + {file = "websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43"}, + {file = "websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217"}, + {file = "websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737"}, + {file = "websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7"}, + {file = "websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:820fb8450edddae3812fd58cbc08e2bf22812cb248ecb5f06dbb82119a56e869"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:125f22dbefaf1554fea66fc83851490edb284ce4f501d37ffed2752f418332d9"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30bbe120437b5648a77d3519b7024ea09530e0b5b18d3698c5a0ae536fe0cc2e"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6b9dadbef0cccd9f4c4ee96b08898afa73e26803bbe0f6aeb5bb12b0074206d"}, + {file = "websockets-16.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56cd5fc4f10a9ea8aa0804bddb7b42506cf9e136046f3b4c27de8fec9e2ecba5"}, + {file = "websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3"}, + {file = "websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57"}, ] [[package]] @@ -7106,13 +6866,13 @@ h11 = ">=0.16.0,<1" [[package]] name = "xero-python" -version = "14.0.0" +version = "15.0.0" description = "Official Python sdk for Xero API generated by OpenAPI spec for oAuth2" optional = false python-versions = ">=3.5" groups = ["main"] files = [ - {file = "xero_python-14.0.0.tar.gz", hash = "sha256:c5740ccc176b6af67e6f527818b54519c661d089e1128abf751ff440cd5acab7"}, + {file = "xero_python-15.0.0.tar.gz", hash = "sha256:7aa866e166cbb29caf477f86e4936db5895dce85e470fb18aad54759e5fab147"}, ] [package.dependencies] @@ -7123,141 +6883,117 @@ urllib3 = "*" [[package]] name = "yarl" -version = "1.23.0" +version = "1.24.5" description = "Yet another URL library" optional = false python-versions = ">=3.10" groups = ["main"] markers = "python_version < \"3.14\"" files = [ - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, - {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, - {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, - {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, - {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, - {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, - {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, - {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, - {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, - {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, - {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, - {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, - {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, - {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, - {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, - {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, - {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, - {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, - {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, - {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, - {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, - {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, - {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, - {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2"}, + {file = "yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba"}, + {file = "yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6"}, + {file = "yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d"}, + {file = "yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224"}, + {file = "yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a"}, + {file = "yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e"}, + {file = "yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077"}, + {file = "yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd"}, + {file = "yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25"}, + {file = "yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec"}, + {file = "yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9"}, + {file = "yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce"}, + {file = "yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba"}, + {file = "yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b"}, + {file = "yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb"}, + {file = "yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16"}, + {file = "yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144"}, + {file = "yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4"}, + {file = "yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740"}, + {file = "yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d"}, + {file = "yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9"}, + {file = "yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5"}, + {file = "yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4"}, + {file = "yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad"}, + {file = "yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba"}, + {file = "yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a"}, + {file = "yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6"}, + {file = "yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047"}, + {file = "yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104"}, + {file = "yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688"}, + {file = "yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7"}, + {file = "yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f"}, ] [package.dependencies] @@ -7267,25 +7003,25 @@ propcache = ">=0.2.1" [[package]] name = "zipp" -version = "3.23.1" +version = "4.1.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc"}, - {file = "zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110"}, + {file = "zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f"}, + {file = "zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.14)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] +enabler = ["pytest-enabler (>=3.4)"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] +type = ["pytest-mypy (>=1.0.1) ; platform_python_implementation != \"PyPy\""] [metadata] lock-version = "2.1" python-versions = "^3.12" -content-hash = "756da8093f691b661edcbaf99d80034204232c29da81ba80ca424bb1036075b9" +content-hash = "a6042515a85359060e548e0e7d96a6f2e5b0267e9ba6295921844a4978ca4fdd" diff --git a/pyproject.toml b/pyproject.toml index fa033722d..4c9a066f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ pytz = "^2026.1" python-dotenv = "^1.1.1" xero-python = ">=10.0.0" requests = "^2.33.0" -django-filter = "^25.2" +django-filter = "^26.1" djangorestframework = "^3.16.0" psycopg = {version = "^3.2", extras = ["binary"]} concurrent-log-handler = "^0.9.29" @@ -25,14 +25,14 @@ reportlab = ">=4.2.5,<6.0.0" pypdf = "^6.4.0" # Modern PDF handling (PyPDF2 replacement, same API) pydantic = "^2.13.4" matplotlib = "^3.10.0" -pyngrok = "^7.2.3" +pyngrok = "^8.1.2" thefuzz = "^0.22.1" python-levenshtein = "^0.27.3" pdf2image = "^1.17.0" gunicorn = "^26.0.0" pdfplumber = "^0.11.6" google-genai = ">=1.19,<3.0" -holidays = ">=0.93,<0.101" +holidays = ">=0.93,<0.102" djangorestframework-simplejwt = "^5.5.1" beautifulsoup4 = "^4.13.4" selenium = "^4.34.2" @@ -40,7 +40,6 @@ lxml = "^6.0.2" django-celery-beat = "^2.6" django-celery-results = "^2.5" google-api-python-client = "^2.171.0" -google-generativeai = "^0.8.5" openpyxl = "^3.1.5" faker = "^40.11.1" mistralai = "^2.1.3" @@ -48,13 +47,13 @@ django-mcp-server = "^0.5.4" channels = "^4.3.0" # 4.3.0+ required for Django 6 support channels-redis = "^4.3.0" mcp = "^1.9.4" -anthropic = ">=0.89,<0.113" +anthropic = ">=0.89,<0.121" pandas = "^3.0.1" numpy = "^2.3.0" httpx = "^0.28.1" pre-commit = "^4.5.1" django-stubs = ">=6.0.1" -drf-spectacular = "^0.29.0" +drf-spectacular = "^0.30.0" httpcore = "^1.0.9" litellm = { version = "^1.83.7", python = ">=3.12,<3.14" } python-stdnum = "^2.1" @@ -83,13 +82,13 @@ pytest = "^9.0.2" flake8-pyproject = "^1.2.3" autoflake = "^2.3.1" autopep8 = "^2.3.2" -ruff = "^0.15.8" +ruff = "^0.16.0" types-requests = "^2.33.0.20260327" types-python-dateutil = "^2.9.0.20250516" black = "^26.3.1" toml = "^0.10.2" tabulate = "^0.10.0" -google-api-python-client-stubs = "1.36.0" +google-api-python-client-stubs = "1.39.0" google-auth-stubs = "^0.3.0" debugpy = "^1.8.19" pytest-django = "^4.12.0" diff --git a/requirements.txt b/requirements.txt index 9deb27a0d..85de01685 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,156 +1,148 @@ # autogenerated from poetry.lock via poetry export -aiohappyeyeballs==2.6.1 ; python_version >= "3.12" and python_version < "3.14" -aiohttp==3.14.1 ; python_version >= "3.12" and python_version < "3.14" +aiohappyeyeballs==2.7.1 ; python_version >= "3.12" and python_version < "3.14" +aiohttp==3.14.3 ; python_version >= "3.12" and python_version < "3.14" aiosignal==1.4.0 ; python_version >= "3.12" and python_version < "3.14" amqp==5.3.1 ; python_version >= "3.12" and python_version < "4.0" -annotated-doc==0.0.4 ; python_version >= "3.12" and python_version < "3.14" -annotated-types==0.7.0 ; python_version >= "3.12" and python_version < "4.0" -anthropic==0.112.0 ; python_version >= "3.12" and python_version < "4.0" -anyio==4.13.0 ; python_version >= "3.12" and python_version < "4.0" -asgiref==3.11.1 ; python_version >= "3.12" and python_version < "4.0" -ast-serialize==0.4.0 ; python_version >= "3.12" and python_version < "4.0" +annotated-types==0.8.0 ; python_version >= "3.12" and python_version < "4.0" +anthropic==0.120.0 ; python_version >= "3.12" and python_version < "4.0" +anyio==4.14.2 ; python_version >= "3.12" and python_version < "4.0" +asgiref==3.12.1 ; python_version >= "3.12" and python_version < "4.0" +ast-serialize==0.6.0 ; python_version >= "3.12" and python_version < "4.0" astroid==4.0.4 ; python_version >= "3.12" and python_version < "4.0" attrs==26.1.0 ; python_version >= "3.12" and python_version < "4.0" autoflake==2.3.3 ; python_version >= "3.12" and python_version < "4.0" autopep8==2.3.2 ; python_version >= "3.12" and python_version < "4.0" -beautifulsoup4==4.14.3 ; python_version >= "3.12" and python_version < "4.0" +beautifulsoup4==4.15.0 ; python_version >= "3.12" and python_version < "4.0" billiard==4.2.4 ; python_version >= "3.12" and python_version < "4.0" black==26.5.1 ; python_version >= "3.12" and python_version < "4.0" blinker==1.9.0 ; python_version >= "3.12" and python_version < "4.0" -cachetools==7.1.4 ; python_version >= "3.12" and python_version < "4.0" +cachetools==7.1.6 ; python_version >= "3.12" and python_version < "4.0" celery==5.6.3 ; python_version >= "3.12" and python_version < "4.0" -certifi==2026.6.17 ; python_version >= "3.12" and python_version < "4.0" -cffi==2.0.0 ; python_version >= "3.12" and python_version < "4.0" and (os_name == "nt" or platform_python_implementation != "PyPy") and (implementation_name != "pypy" or platform_python_implementation != "PyPy") +certifi==2026.7.22 ; python_version >= "3.12" and python_version < "4.0" +cffi==2.1.0 ; python_version >= "3.12" and python_version < "4.0" and (os_name == "nt" or platform_python_implementation != "PyPy") and (implementation_name != "pypy" or platform_python_implementation != "PyPy") cfgv==3.5.0 ; python_version >= "3.12" and python_version < "4.0" channels-redis==4.3.0 ; python_version >= "3.12" and python_version < "4.0" channels==4.3.2 ; python_version >= "3.12" and python_version < "4.0" -charset-normalizer==3.4.7 ; python_version >= "3.12" and python_version < "4.0" +charset-normalizer==3.4.9 ; python_version >= "3.12" and python_version < "4.0" click-didyoumean==0.3.1 ; python_version >= "3.12" and python_version < "4.0" click-plugins==1.1.1.2 ; python_version >= "3.12" and python_version < "4.0" click-repl==0.3.0 ; python_version >= "3.12" and python_version < "4.0" -click==8.3.3 ; python_version >= "3.12" and python_version < "4.0" +click==8.4.2 ; python_version >= "3.12" and python_version < "4.0" colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" concurrent-log-handler==0.9.29 ; python_version >= "3.12" and python_version < "4.0" contourpy==1.3.3 ; python_version >= "3.12" and python_version < "4.0" cron-descriptor==1.4.5 ; python_version >= "3.12" and python_version < "4.0" cryptography==49.0.0 ; python_version >= "3.12" and python_version < "4.0" cycler==0.12.1 ; python_version >= "3.12" and python_version < "4.0" -debugpy==1.8.20 ; python_version >= "3.12" and python_version < "4.0" +debugpy==1.8.21 ; python_version >= "3.12" and python_version < "4.0" deptry==0.25.1 ; python_version >= "3.12" and python_version < "4.0" dill==0.4.1 ; python_version >= "3.12" and python_version < "4.0" -distlib==0.4.0 ; python_version >= "3.12" and python_version < "4.0" +distlib==0.4.3 ; python_version >= "3.12" and python_version < "4.0" distro==1.9.0 ; python_version >= "3.12" and python_version < "4.0" django-celery-beat==2.9.0 ; python_version >= "3.12" and python_version < "4.0" django-celery-results==2.6.0 ; python_version >= "3.12" and python_version < "4.0" django-encrypted-model-fields==0.6.5 ; python_version >= "3.12" and python_version < "4.0" django-filter-stubs==0.1.3 ; python_version >= "3.12" and python_version < "4.0" -django-filter==25.2 ; python_version >= "3.12" and python_version < "4.0" +django-filter==26.1 ; python_version >= "3.12" and python_version < "4.0" django-mcp-server==0.5.7 ; python_version >= "3.12" and python_version < "4.0" -django-simple-history==3.11.0 ; python_version >= "3.12" and python_version < "4.0" +django-simple-history==3.13.0 ; python_version >= "3.12" and python_version < "4.0" django-solo==2.5.1 ; python_version >= "3.12" and python_version < "4.0" -django-stubs-ext==6.0.3 ; python_version >= "3.12" and python_version < "4.0" -django-stubs==6.0.3 ; python_version >= "3.12" and python_version < "4.0" -django-timezone-field==7.2.1 ; python_version >= "3.12" and python_version < "4.0" +django-stubs-ext==6.0.7 ; python_version >= "3.12" and python_version < "4.0" +django-stubs==6.0.7 ; python_version >= "3.12" and python_version < "4.0" +django-timezone-field==7.2.2 ; python_version >= "3.12" and python_version < "4.0" django==6.0.7 ; python_version >= "3.12" and python_version < "4.0" djangorestframework-simplejwt==5.5.1 ; python_version >= "3.12" and python_version < "4.0" -djangorestframework-stubs==3.16.9 ; python_version >= "3.12" and python_version < "4.0" +djangorestframework-stubs==3.17.0 ; python_version >= "3.12" and python_version < "4.0" djangorestframework==3.17.1 ; python_version >= "3.12" and python_version < "4.0" docstring-parser==0.18.0 ; python_version >= "3.12" and python_version < "4.0" -drf-spectacular==0.29.0 ; python_version >= "3.12" and python_version < "4.0" -dropbox==12.0.2 ; python_version >= "3.12" and python_version < "4.0" +drf-spectacular==0.30.0 ; python_version >= "3.12" and python_version < "4.0" +dropbox==12.2.1 ; python_version >= "3.12" and python_version < "4.0" et-xmlfile==2.0.0 ; python_version >= "3.12" and python_version < "4.0" eval-type-backport==0.4.0 ; python_version >= "3.12" and python_version < "4.0" -faker==40.15.0 ; python_version >= "3.12" and python_version < "4.0" +faker==40.36.0 ; python_version >= "3.12" and python_version < "4.0" fastuuid==0.14.0 ; python_version >= "3.12" and python_version < "3.14" -filelock==3.29.0 ; python_version >= "3.12" and python_version < "4.0" +filelock==3.32.0 ; python_version >= "3.12" and python_version < "4.0" flake8-pyproject==1.2.4 ; python_version >= "3.12" and python_version < "4.0" flake8==7.3.0 ; python_version >= "3.12" and python_version < "4.0" -fonttools==4.62.1 ; python_version >= "3.12" and python_version < "4.0" +fonttools==4.63.0 ; python_version >= "3.12" and python_version < "4.0" freezegun==1.5.5 ; python_version >= "3.12" and python_version < "4.0" frozenlist==1.8.0 ; python_version >= "3.12" and python_version < "3.14" fsspec==2026.6.0 ; python_version >= "3.12" and python_version < "3.14" -google-ai-generativelanguage==0.6.15 ; python_version >= "3.12" and python_version < "4.0" -google-api-core==2.25.2 ; python_version >= "3.14" and python_version < "4.0" -google-api-core==2.30.3 ; python_version >= "3.12" and python_version < "3.14" -google-api-python-client-stubs==1.36.0 ; python_version >= "3.12" and python_version < "4.0" -google-api-python-client==2.195.0 ; python_version >= "3.12" and python_version < "4.0" -google-auth-httplib2==0.3.1 ; python_version >= "3.12" and python_version < "4.0" +google-api-core==2.33.0 ; python_version >= "3.12" and python_version < "4.0" +google-api-python-client-stubs==1.39.0 ; python_version >= "3.12" and python_version < "4.0" +google-api-python-client==2.198.0 ; python_version >= "3.12" and python_version < "4.0" +google-auth-httplib2==0.4.0 ; python_version >= "3.12" and python_version < "4.0" google-auth-stubs==0.3.0 ; python_version >= "3.12" and python_version < "4.0" -google-auth==2.53.0 ; python_version >= "3.12" and python_version < "4.0" -google-genai==2.10.0 ; python_version >= "3.12" and python_version < "4.0" -google-generativeai==0.8.6 ; python_version >= "3.12" and python_version < "4.0" -googleapis-common-protos==1.74.0 ; python_version >= "3.12" and python_version < "4.0" +google-auth==2.56.2 ; python_version >= "3.12" and python_version < "4.0" +google-genai==2.14.0 ; python_version >= "3.12" and python_version < "4.0" +googleapis-common-protos==1.75.0 ; python_version >= "3.12" and python_version < "4.0" grpc-stubs==1.53.0.6 ; python_version >= "3.12" and python_version < "4.0" -grpcio-status==1.71.2 ; python_version >= "3.12" and python_version < "4.0" -grpcio==1.80.0 ; python_version >= "3.12" and python_version < "4.0" +grpcio==1.83.0 ; python_version >= "3.12" and python_version < "4.0" gunicorn==26.0.0 ; python_version >= "3.12" and python_version < "4.0" h11==0.16.0 ; python_version >= "3.12" and python_version < "4.0" -hf-xet==1.4.3 ; python_version >= "3.12" and python_version < "3.14" and (platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "arm64" or platform_machine == "aarch64") -holidays==0.100 ; python_version >= "3.12" and python_version < "4.0" +hf-xet==1.5.2 ; python_version >= "3.12" and python_version < "3.14" and (platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "arm64" or platform_machine == "aarch64") +holidays==0.101 ; python_version >= "3.12" and python_version < "4.0" httpcore==1.0.9 ; python_version >= "3.12" and python_version < "4.0" -httplib2==0.31.2 ; python_version >= "3.12" and python_version < "4.0" +httplib2==0.32.0 ; python_version >= "3.12" and python_version < "4.0" httpx-sse==0.4.3 ; python_version >= "3.12" and python_version < "4.0" httpx==0.28.1 ; python_version >= "3.12" and python_version < "4.0" -huggingface-hub==1.13.0 ; python_version >= "3.12" and python_version < "3.14" +huggingface-hub==1.25.1 ; python_version >= "3.12" and python_version < "3.14" identify==2.6.19 ; python_version >= "3.12" and python_version < "4.0" idna==3.18 ; python_version >= "3.12" and python_version < "4.0" importlib-metadata==8.7.1 ; python_version >= "3.12" and python_version < "4.0" inflection==0.5.1 ; python_version >= "3.12" and python_version < "4.0" iniconfig==2.3.0 ; python_version >= "3.12" and python_version < "4.0" isort==8.0.1 ; python_version >= "3.12" and python_version < "4.0" -jinja2==3.1.6 ; python_version >= "3.12" and python_version < "3.14" -jiter==0.15.0 ; python_version >= "3.12" and python_version < "4.0" +jinja2==3.1.6 ; python_version >= "3.12" and python_version < "4.0" +jiter==0.16.0 ; python_version >= "3.12" and python_version < "4.0" jsonpath-python==1.1.6 ; python_version >= "3.12" and python_version < "4.0" jsonschema-specifications==2025.9.1 ; python_version >= "3.12" and python_version < "4.0" jsonschema==4.26.0 ; python_version >= "3.12" and python_version < "4.0" kiwisolver==1.5.0 ; python_version >= "3.12" and python_version < "4.0" kombu==5.6.2 ; python_version >= "3.12" and python_version < "4.0" levenshtein==0.27.3 ; python_version >= "3.12" and python_version < "4.0" -librt==0.11.0 ; python_version >= "3.12" and python_version < "4.0" and platform_python_implementation != "PyPy" -litellm==1.88.1 ; python_version >= "3.12" and python_version < "3.14" -lxml==6.1.0 ; python_version >= "3.12" and python_version < "4.0" -markdown-it-py==4.0.0 ; python_version >= "3.12" and python_version < "3.14" -markupsafe==3.0.3 ; python_version >= "3.12" and python_version < "3.14" -matplotlib==3.10.9 ; python_version >= "3.12" and python_version < "4.0" +librt==0.13.0 ; python_version >= "3.12" and python_version < "4.0" and platform_python_implementation != "PyPy" +litellm==1.93.0 ; python_version >= "3.12" and python_version < "3.14" +lxml==6.1.1 ; python_version >= "3.12" and python_version < "4.0" +markupsafe==3.0.3 ; python_version >= "3.12" and python_version < "4.0" +matplotlib==3.11.1 ; python_version >= "3.12" and python_version < "4.0" mccabe==0.7.0 ; python_version >= "3.12" and python_version < "4.0" mcp==1.28.1 ; python_version >= "3.12" and python_version < "4.0" -mdurl==0.1.2 ; python_version >= "3.12" and python_version < "3.14" -mistralai==2.6.0 ; python_version >= "3.12" and python_version < "4.0" +mistralai==2.7.2 ; python_version >= "3.12" and python_version < "4.0" msgpack==1.2.1 ; python_version >= "3.12" and python_version < "4.0" multidict==6.7.1 ; python_version >= "3.12" and python_version < "3.14" mypy-baseline==0.7.4 ; python_version >= "3.12" and python_version < "4.0" mypy-extensions==1.1.0 ; python_version >= "3.12" and python_version < "4.0" -mypy==2.1.0 ; python_version >= "3.12" and python_version < "4.0" +mypy==2.3.0 ; python_version >= "3.12" and python_version < "4.0" nicknames==1.0.1 ; python_version >= "3.12" and python_version < "4.0" nodeenv==1.10.0 ; python_version >= "3.12" and python_version < "4.0" nplus1==1.1.5 ; python_version >= "3.12" and python_version < "4.0" -numpy==2.4.4 ; python_version >= "3.12" and python_version < "4.0" -openai==2.24.0 ; python_version >= "3.12" and python_version < "3.14" +numpy==2.5.1 ; python_version >= "3.12" and python_version < "4.0" +openai==2.48.0 ; python_version >= "3.12" and python_version < "3.14" openpyxl==3.1.5 ; python_version >= "3.12" and python_version < "4.0" opentelemetry-api==1.39.1 ; python_version >= "3.12" and python_version < "4.0" opentelemetry-semantic-conventions==0.60b1 ; python_version >= "3.12" and python_version < "4.0" outcome==1.3.0.post0 ; python_version >= "3.12" and python_version < "4.0" packaging==26.2 ; python_version >= "3.12" and python_version < "4.0" pandas-stubs==3.0.3.260530 ; python_version >= "3.12" and python_version < "4.0" -pandas==3.0.2 ; python_version >= "3.12" and python_version < "4.0" +pandas==3.0.5 ; python_version >= "3.12" and python_version < "4.0" pathspec==1.1.1 ; python_version >= "3.12" and python_version < "4.0" pdf2image==1.17.0 ; python_version >= "3.12" and python_version < "4.0" -pdfminer-six==20251230 ; python_version >= "3.12" and python_version < "4.0" -pdfplumber==0.11.9 ; python_version >= "3.12" and python_version < "4.0" -pillow==12.2.0 ; python_version >= "3.12" and python_version < "4.0" -platformdirs==4.9.6 ; python_version >= "3.12" and python_version < "4.0" +pdfminer-six==20260107 ; python_version >= "3.12" and python_version < "4.0" +pdfplumber==0.11.10 ; python_version >= "3.12" and python_version < "4.0" +pillow==12.3.0 ; python_version >= "3.12" and python_version < "4.0" +platformdirs==4.11.0 ; python_version >= "3.12" and python_version < "4.0" pluggy==1.6.0 ; python_version >= "3.12" and python_version < "4.0" -ply==3.11 ; python_version >= "3.12" and python_version < "4.0" portalocker==3.2.0 ; python_version >= "3.12" and python_version < "4.0" -pre-commit==4.6.0 ; python_version >= "3.12" and python_version < "4.0" -prompt-toolkit==3.0.52 ; python_version >= "3.12" and python_version < "4.0" -propcache==0.4.1 ; python_version >= "3.12" and python_version < "3.14" -proto-plus==1.27.2 ; python_version >= "3.12" and python_version < "4.0" -protobuf==5.29.6 ; python_version >= "3.12" and python_version < "4.0" +pre-commit==4.6.1 ; python_version >= "3.12" and python_version < "4.0" +prompt-toolkit==3.0.53 ; python_version >= "3.12" and python_version < "4.0" +propcache==0.5.2 ; python_version >= "3.12" and python_version < "3.14" +proto-plus==1.28.2 ; python_version >= "3.12" and python_version < "4.0" +protobuf==7.35.1 ; python_version >= "3.12" and python_version < "4.0" psycopg-binary==3.3.4 ; python_version >= "3.12" and python_version < "4.0" and implementation_name != "pypy" psycopg==3.3.4 ; python_version >= "3.12" and python_version < "4.0" pyasn1-modules==0.4.2 ; python_version >= "3.12" and python_version < "4.0" -pyasn1==0.6.3 ; python_version >= "3.12" and python_version < "4.0" +pyasn1==0.6.4 ; python_version >= "3.12" and python_version < "4.0" pycodestyle==2.14.0 ; python_version >= "3.12" and python_version < "4.0" pycparser==3.0 ; python_version >= "3.12" and python_version < "4.0" and (platform_python_implementation != "PyPy" or os_name == "nt") and (platform_python_implementation != "PyPy" or implementation_name != "pypy" and implementation_name != "PyPy") and implementation_name != "PyPy" pydantic-core==2.46.4 ; python_version >= "3.12" and python_version < "4.0" @@ -162,82 +154,79 @@ pyjwt==2.13.0 ; python_version >= "3.12" and python_version < "4.0" pylint-django==2.8.0 ; python_version >= "3.12" and python_version < "4.0" pylint-plugin-utils==0.9.0 ; python_version >= "3.12" and python_version < "4.0" pylint==4.0.6 ; python_version >= "3.12" and python_version < "4.0" -pyngrok==7.5.1 ; python_version >= "3.12" and python_version < "4.0" +pyngrok==8.1.2 ; python_version >= "3.12" and python_version < "4.0" pyparsing==3.3.2 ; python_version >= "3.12" and python_version < "4.0" pypdf==6.14.2 ; python_version >= "3.12" and python_version < "4.0" -pypdfium2==5.7.1 ; python_version >= "3.12" and python_version < "4.0" -pyproject-api==1.10.1 ; python_version >= "3.12" and python_version < "4.0" +pypdfium2==5.12.1 ; python_version >= "3.12" and python_version < "4.0" +pyproject-api==1.11.0 ; python_version >= "3.12" and python_version < "4.0" pysocks==1.7.1 ; python_version >= "3.12" and python_version < "4.0" pytest-django==4.12.0 ; python_version >= "3.12" and python_version < "4.0" -pytest==9.0.3 ; python_version >= "3.12" and python_version < "4.0" +pytest==9.1.1 ; python_version >= "3.12" and python_version < "4.0" python-crontab==3.3.0 ; python_version >= "3.12" and python_version < "4.0" python-dateutil==2.9.0.post0 ; python_version >= "3.12" and python_version < "4.0" -python-discovery==1.4.2 ; python_version >= "3.12" and python_version < "4.0" +python-discovery==1.5.0 ; python_version >= "3.12" and python_version < "4.0" python-docx==1.2.0 ; python_version >= "3.12" and python_version < "4.0" python-dotenv==1.2.2 ; python_version >= "3.12" and python_version < "4.0" python-levenshtein==0.27.3 ; python_version >= "3.12" and python_version < "4.0" python-multipart==0.0.32 ; python_version >= "3.12" and python_version < "4.0" python-stdnum==2.2 ; python_version >= "3.12" and python_version < "4.0" pytokens==0.4.1 ; python_version >= "3.12" and python_version < "4.0" -pytz==2026.2 ; python_version >= "3.12" and python_version < "4.0" -pywin32==311 ; python_version >= "3.12" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows") +pytz==2026.3.post1 ; python_version >= "3.12" and python_version < "4.0" +pywin32==312 ; python_version >= "3.12" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows") pyyaml==6.0.3 ; python_version >= "3.12" and python_version < "4.0" rapidfuzz==3.14.5 ; python_version >= "3.12" and python_version < "4.0" -redis==7.4.0 ; python_version >= "3.12" and python_version < "4.0" +redis==8.0.1 ; python_version >= "3.12" and python_version < "4.0" referencing==0.37.0 ; python_version >= "3.12" and python_version < "4.0" -regex==2026.4.4 ; python_version >= "3.12" and python_version < "3.14" +regex==2026.7.19 ; python_version >= "3.12" and python_version < "3.14" reportlab==5.0.0 ; python_version >= "3.12" and python_version < "4.0" requests==2.34.2 ; python_version >= "3.12" and python_version < "4.0" -requirements-parser==0.13.0 ; python_version >= "3.12" and python_version < "4.0" -rich==15.0.0 ; python_version >= "3.12" and python_version < "3.14" -rpds-py==0.30.0 ; python_version >= "3.12" and python_version < "4.0" -ruff==0.15.12 ; python_version >= "3.12" and python_version < "4.0" -selenium==4.43.0 ; python_version >= "3.12" and python_version < "4.0" -setuptools==82.0.1 ; python_version >= "3.12" and python_version < "4.0" -shellingham==1.5.4 ; python_version >= "3.12" and python_version < "3.14" +requirements-parser==0.13.1 ; python_version >= "3.12" and python_version < "4.0" +rpds-py==2026.6.3 ; python_version >= "3.12" and python_version < "4.0" +ruff==0.16.0 ; python_version >= "3.12" and python_version < "4.0" +selenium==4.46.0 ; python_version >= "3.12" and python_version < "4.0" +setuptools==83.0.0 ; python_version >= "3.12" and python_version < "4.0" six==1.17.0 ; python_version >= "3.12" and python_version < "4.0" sniffio==1.3.1 ; python_version >= "3.12" and python_version < "4.0" sortedcontainers==2.4.0 ; python_version >= "3.12" and python_version < "4.0" -soupsieve==2.8.4 ; python_version >= "3.12" and python_version < "4.0" +soupsieve==2.9.1 ; python_version >= "3.12" and python_version < "4.0" sqlparse==0.5.5 ; python_version >= "3.12" and python_version < "4.0" -sse-starlette==3.4.1 ; python_version >= "3.12" and python_version < "4.0" +sse-starlette==3.4.6 ; python_version >= "3.12" and python_version < "4.0" starlette==1.3.1 ; python_version >= "3.12" and python_version < "4.0" -stone==3.3.1 ; python_version >= "3.12" and python_version < "4.0" +stone==3.5.3 ; python_version >= "3.12" and python_version < "4.0" tabulate==0.10.0 ; python_version >= "3.12" and python_version < "4.0" tenacity==9.1.4 ; python_version >= "3.12" and python_version < "4.0" thefuzz==0.22.1 ; python_version >= "3.12" and python_version < "4.0" -tiktoken==0.12.0 ; python_version >= "3.12" and python_version < "3.14" -tokenizers==0.22.2 ; python_version >= "3.12" and python_version < "3.14" +tiktoken==0.13.0 ; python_version >= "3.12" and python_version < "3.14" +tokenizers==0.23.1 ; python_version >= "3.12" and python_version < "3.14" toml==0.10.2 ; python_version >= "3.12" and python_version < "4.0" tomli-w==1.2.0 ; python_version >= "3.12" and python_version < "4.0" tomli==2.4.1 ; python_version >= "3.12" and python_version < "3.15" -tomlkit==0.15.0 ; python_version >= "3.12" and python_version < "4.0" -tox==4.53.1 ; python_version >= "3.12" and python_version < "4.0" -tqdm==4.67.3 ; python_version >= "3.12" and python_version < "4.0" +tomlkit==0.15.1 ; python_version >= "3.12" and python_version < "4.0" +tox==4.58.0 ; python_version >= "3.12" and python_version < "4.0" +tqdm==4.70.0 ; python_version >= "3.12" and python_version < "3.14" trio-websocket==0.12.2 ; python_version >= "3.12" and python_version < "4.0" trio==0.33.0 ; python_version >= "3.12" and python_version < "4.0" -typer==0.23.1 ; python_version >= "3.12" and python_version < "3.14" -types-httplib2==0.31.2.20260408 ; python_version >= "3.12" and python_version < "4.0" +types-httplib2==0.32.0.20260720 ; python_version >= "3.12" and python_version < "4.0" types-jsonschema==4.26.0.20260518 ; python_version >= "3.12" and python_version < "4.0" -types-python-dateutil==2.9.0.20260408 ; python_version >= "3.12" and python_version < "4.0" -types-pytz==2026.1.1.20260408 ; python_version >= "3.12" and python_version < "4.0" -types-pyyaml==6.0.12.20260408 ; python_version >= "3.12" and python_version < "4.0" -types-reportlab==4.5.1.20260521 ; python_version >= "3.12" and python_version < "4.0" -types-requests==2.33.0.20260503 ; python_version >= "3.12" and python_version < "4.0" -typing-extensions==4.15.0 ; python_version >= "3.12" and python_version < "4.0" +types-python-dateutil==2.9.0.20260716 ; python_version >= "3.12" and python_version < "4.0" +types-pytz==2026.3.1.20260727 ; python_version >= "3.12" and python_version < "4.0" +types-pyyaml==6.0.12.20260724 ; python_version >= "3.12" and python_version < "4.0" +types-reportlab==4.5.1.20260724 ; python_version >= "3.12" and python_version < "4.0" +types-requests==2.33.0.20260712 ; python_version >= "3.12" and python_version < "4.0" +typing-extensions==4.16.0 ; python_version >= "3.12" and python_version < "4.0" typing-inspection==0.4.2 ; python_version >= "3.12" and python_version < "4.0" -tzdata==2026.2 ; python_version >= "3.12" and python_version < "4.0" -tzlocal==5.3.1 ; python_version >= "3.12" and python_version < "4.0" +tzdata==2026.3 ; python_version >= "3.12" and python_version < "4.0" +tzlocal==5.4.4 ; python_version >= "3.12" and python_version < "4.0" uritemplate==4.2.0 ; python_version >= "3.12" and python_version < "4.0" urllib3==2.7.0 ; python_version >= "3.12" and python_version < "4.0" -uvicorn==0.46.0 ; python_version >= "3.12" and python_version < "4.0" and sys_platform != "emscripten" +uvicorn==0.51.0 ; python_version >= "3.12" and python_version < "4.0" and sys_platform != "emscripten" vine==5.1.0 ; python_version >= "3.12" and python_version < "4.0" -virtualenv==21.5.1 ; python_version >= "3.12" and python_version < "4.0" +virtualenv==21.7.0 ; python_version >= "3.12" and python_version < "4.0" vulture==2.16 ; python_version >= "3.12" and python_version < "4.0" -wcwidth==0.7.0 ; python_version >= "3.12" and python_version < "4.0" +wcwidth==0.8.2 ; python_version >= "3.12" and python_version < "4.0" websocket-client==1.9.0 ; python_version >= "3.12" and python_version < "4.0" -websockets==16.0 ; python_version >= "3.12" and python_version < "4.0" +websockets==16.1.1 ; python_version >= "3.12" and python_version < "4.0" wsproto==1.3.2 ; python_version >= "3.12" and python_version < "4.0" -xero-python==14.0.0 ; python_version >= "3.12" and python_version < "4.0" -yarl==1.23.0 ; python_version >= "3.12" and python_version < "3.14" -zipp==3.23.1 ; python_version >= "3.12" and python_version < "4.0" +xero-python==15.0.0 ; python_version >= "3.12" and python_version < "4.0" +yarl==1.24.5 ; python_version >= "3.12" and python_version < "3.14" +zipp==4.1.0 ; python_version >= "3.12" and python_version < "4.0" diff --git a/scripts/check_mypy.sh b/scripts/check_mypy.sh index 8d7341e3e..b535b47e4 100755 --- a/scripts/check_mypy.sh +++ b/scripts/check_mypy.sh @@ -2,7 +2,9 @@ # Authoritative backend type check (KAN-205). # Runs full-strict mypy and fails on any error not recorded in mypy-baseline.txt. # After fixing baselined errors, shrink the baseline with: -# poetry run mypy apps/ docketworks/ | poetry run mypy-baseline sync +# poetry run mypy apps/ docketworks/ | poetry run mypy-baseline sync --sort-baseline +# --sort-baseline keeps the file in canonical order so diffs show only real +# add/remove, not mypy's output-order reshuffling. # # mypy exits 1 whenever the (baselined) errors exist, so its exit code is not # the gate — mypy-baseline filter's is. We still guard against mypy crashing diff --git a/scripts/server/README.md b/scripts/server/README.md index 7b20e7730..e2c2170c1 100644 --- a/scripts/server/README.md +++ b/scripts/server/README.md @@ -133,17 +133,15 @@ What `deploy.sh` does, in order: ### Choosing what to deploy -`deploy.sh` resolves `origin/production` by default. To verify a release -candidate on UAT, deploy any ref explicitly: +Production servers typically track `origin/production`; testing and UAT servers +typically track `origin/main`: ```bash sudo ./scripts/server/deploy.sh mycompany-uat --ref origin/main ``` -`instance.sh create` takes the same `--ref` (default `origin/production`) to -build a new instance's first release from a candidate; re-point an existing -instance with `deploy.sh --ref`. Nothing persists the ref per instance, so -boot-time catch-up returns servers to `production` (ADR 0029). +Use `instance.sh create --ref origin/main` for a new UAT instance; re-point an +existing instance with `deploy.sh --ref`. A non-production `--ref` on a `*-prod` instance is refused unless acknowledged (interactive `y/N`, or `--allow-prod-ref` non-interactively) — a merged hotfix diff --git a/scripts/server/common.sh b/scripts/server/common.sh index 3c5930db9..0f81bcbcf 100755 --- a/scripts/server/common.sh +++ b/scripts/server/common.sh @@ -31,7 +31,7 @@ validate_env() { exit 1 } -# Guard: a prod instance only ever runs origin/production (ADR 0029). A +# Guard: a prod instance normally tracks origin/production (ADR 0029). A # non-production ref on a *-prod instance is almost always an accident (e.g. a # --ref copied from a UAT command), so refuse unless explicitly acknowledged: # interactively with a y/N prompt, or non-interactively with allow="true"/"1". @@ -62,7 +62,7 @@ require_production_ref_or_ack() { fi fi - echo "ERROR: prod instances run origin/production (ADR 0029)." >&2 + echo "ERROR: prod instances normally track origin/production (ADR 0029)." >&2 echo " Pass --allow-prod-ref to override non-interactively." >&2 exit 1 } diff --git a/scripts/server/deploy.sh b/scripts/server/deploy.sh index 2fcbfb1f5..7fb708242 100755 --- a/scripts/server/deploy.sh +++ b/scripts/server/deploy.sh @@ -11,10 +11,10 @@ set -euo pipefail # # That command fetches GitHub, resolves origin/production to a SHA, builds # /opt/docketworks/releases/ if missing, then switches only the requested -# instance to that release. Servers run the production branch by default -# (ADR 0029); main is the integration branch, deployed to UAT only as a -# release candidate via --ref. A non-production --ref on a *-prod instance is -# refused unless acknowledged (interactive confirm, or --allow-prod-ref). +# instance to that release. Production servers typically track production; +# testing and UAT servers typically track main (ADR 0029). A non-production +# --ref on a *-prod instance is refused unless acknowledged (interactive +# confirm, or --allow-prod-ref). SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SELF="$SCRIPT_DIR/$(basename "$0")" diff --git a/scripts/server/instance.sh b/scripts/server/instance.sh index de52994b0..80b7ed4cb 100755 --- a/scripts/server/instance.sh +++ b/scripts/server/instance.sh @@ -325,19 +325,19 @@ render_phone_provider_settings_fixture() { log "Generating phone provider settings fixture..." mkdir -p "$fixture_dir" - local ESC_PHONE_PROVIDER_USERNAME ESC_PHONE_PROVIDER_PASSWORD ESC_PHONE_PROVIDER_ACCOUNT_CODE - local PHONE_PROVIDER_BASE_URL_JSON - ESC_PHONE_PROVIDER_USERNAME="$(sed_escape "${PHONE_PROVIDER_USERNAME:-}")" - ESC_PHONE_PROVIDER_PASSWORD="$(sed_escape "${PHONE_PROVIDER_PASSWORD:-}")" - ESC_PHONE_PROVIDER_ACCOUNT_CODE="$(sed_escape "${PHONE_PROVIDER_ACCOUNT_CODE:-}")" + local PHONE_PROVIDER_BASE_URL_JSON PHONE_PROVIDER_USERNAME_JSON + local PHONE_PROVIDER_PASSWORD_JSON PHONE_PROVIDER_ACCOUNT_CODE_JSON PHONE_PROVIDER_BASE_URL_JSON="$(sed_escape "$(json_string_or_null "${PHONE_PROVIDER_BASE_URL:-}")")" + PHONE_PROVIDER_USERNAME_JSON="$(sed_escape "$(json_string_or_null "${PHONE_PROVIDER_USERNAME:-}")")" + PHONE_PROVIDER_PASSWORD_JSON="$(sed_escape "$(json_string_or_null "${PHONE_PROVIDER_PASSWORD:-}")")" + PHONE_PROVIDER_ACCOUNT_CODE_JSON="$(sed_escape "$(json_string_or_null "${PHONE_PROVIDER_ACCOUNT_CODE:-}")")" sed \ -e "s|__PHONE_PROVIDER_DOWNLOADS_ENABLED__|${PHONE_PROVIDER_DOWNLOADS_ENABLED:-false}|g" \ -e "s|__PHONE_PROVIDER_RECORDING_DELETION_ENABLED__|${PHONE_PROVIDER_RECORDING_DELETION_ENABLED:-false}|g" \ -e "s|__PHONE_PROVIDER_BASE_URL_JSON__|$PHONE_PROVIDER_BASE_URL_JSON|g" \ - -e "s|__PHONE_PROVIDER_USERNAME__|$ESC_PHONE_PROVIDER_USERNAME|g" \ - -e "s|__PHONE_PROVIDER_PASSWORD__|$ESC_PHONE_PROVIDER_PASSWORD|g" \ - -e "s|__PHONE_PROVIDER_ACCOUNT_CODE__|$ESC_PHONE_PROVIDER_ACCOUNT_CODE|g" \ + -e "s|__PHONE_PROVIDER_USERNAME_JSON__|$PHONE_PROVIDER_USERNAME_JSON|g" \ + -e "s|__PHONE_PROVIDER_PASSWORD_JSON__|$PHONE_PROVIDER_PASSWORD_JSON|g" \ + -e "s|__PHONE_PROVIDER_ACCOUNT_CODE_JSON__|$PHONE_PROVIDER_ACCOUNT_CODE_JSON|g" \ "$TEMPLATE_DIR/phone-provider-settings.json.template" \ > "$fixture_dir/phone_provider_settings.json" chown -R "$instance_user:$instance_user" "$fixture_dir" @@ -1004,6 +1004,10 @@ do_list() { # ============================================================ # main # ============================================================ +if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then + return 0 +fi + if [[ $# -lt 1 ]]; then echo "Usage: $0 {prepare-config|create|reconfigure|destroy|status|list} [args...]" echo " prepare-config [--seed]" diff --git a/scripts/server/templates/phone-provider-settings.json.template b/scripts/server/templates/phone-provider-settings.json.template index ccb6fdc54..99b5c8017 100644 --- a/scripts/server/templates/phone-provider-settings.json.template +++ b/scripts/server/templates/phone-provider-settings.json.template @@ -6,9 +6,9 @@ "downloads_enabled": __PHONE_PROVIDER_DOWNLOADS_ENABLED__, "recording_deletion_enabled": __PHONE_PROVIDER_RECORDING_DELETION_ENABLED__, "base_url": __PHONE_PROVIDER_BASE_URL_JSON__, - "username": "__PHONE_PROVIDER_USERNAME__", - "password": "__PHONE_PROVIDER_PASSWORD__", - "account_code": "__PHONE_PROVIDER_ACCOUNT_CODE__", + "username": __PHONE_PROVIDER_USERNAME_JSON__, + "password": __PHONE_PROVIDER_PASSWORD_JSON__, + "account_code": __PHONE_PROVIDER_ACCOUNT_CODE_JSON__, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" }
    @@ -302,17 +302,11 @@ const filters = ref({ endDate: '', }) -const coreSummary = computed(() => summary.value.find((s) => s.rdti_type === 'core')) -const supportingSummary = computed(() => summary.value.find((s) => s.rdti_type === 'supporting')) +const coreSummary = computed(() => summary.value.find((s) => s.rdti_type === 'core_rd')) +const supportingSummary = computed(() => summary.value.find((s) => s.rdti_type === 'supporting_rd')) type SortableColumn = - | 'job_number' - | 'job_name' - | 'company_name' - | 'rdti_type' - | 'hours' - | 'cost' - | 'revenue' + 'job_number' | 'job_name' | 'company_name' | 'rdti_type' | 'hours' | 'cost' | 'revenue' const sortColumn = ref(null) const sortDirection = ref<'asc' | 'desc'>('asc') diff --git a/frontend/src/services/__tests__/job.service.search.test.ts b/frontend/src/services/__tests__/job.service.search.test.ts new file mode 100644 index 000000000..a99256039 --- /dev/null +++ b/frontend/src/services/__tests__/job.service.search.test.ts @@ -0,0 +1,39 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { DEFAULT_ADVANCED_FILTERS } from '@/constants/advanced-filters' + +const { advancedSearch } = vi.hoisted(() => ({ + advancedSearch: vi.fn(), +})) + +vi.mock('@/api/client', () => ({ + api: { + job_jobs_advanced_search_retrieve: advancedSearch, + }, +})) + +import { jobService } from '@/services/job.service' + +describe('jobService advanced search', () => { + beforeEach(() => { + vi.clearAllMocks() + advancedSearch.mockResolvedValue({ jobs: [] }) + }) + + it('normalizes status arrays at the API boundary', async () => { + await jobService.performAdvancedSearch({ + ...DEFAULT_ADVANCED_FILTERS, + status: ['draft', 'approved'], + }) + await jobService.performAdvancedSearch({ + ...DEFAULT_ADVANCED_FILTERS, + status: [], + }) + + expect(advancedSearch).toHaveBeenNthCalledWith(1, { + queries: expect.objectContaining({ status: 'draft,approved' }), + }) + expect(advancedSearch).toHaveBeenNthCalledWith(2, { + queries: expect.objectContaining({ status: '' }), + }) + }) +}) diff --git a/frontend/src/services/costline.service.ts b/frontend/src/services/costline.service.ts index e79ef3f7b..ef4be6851 100644 --- a/frontend/src/services/costline.service.ts +++ b/frontend/src/services/costline.service.ts @@ -42,6 +42,10 @@ export const createCostLine = async ( ext_refs: payload.ext_refs ?? {}, meta: payload.meta ?? {}, accounting_date: payload.accounting_date ?? now, + // An absent description is null, never ''. Callers building a row from + // form state pass '' for an untouched field, so normalise it here rather + // than in each of them. + ...('desc' in payload ? { desc: payload.desc || null } : {}), } const result = @@ -61,7 +65,12 @@ export const updateCostLine = async ( id: string, payload: PatchedCostLineRequest, ): Promise => { - const result = await api.job_cost_lines_partial_update(payload, { + // As in create: a cleared description is null, never ''. + const body: PatchedCostLineRequest = { + ...payload, + ...('desc' in payload ? { desc: payload.desc || null } : {}), + } + const result = await api.job_cost_lines_partial_update(body, { params: { cost_line_id: id }, }) return schemas.CostLine.parse(result) diff --git a/frontend/src/services/job.service.ts b/frontend/src/services/job.service.ts index 5420fcc92..c2ceca912 100644 --- a/frontend/src/services/job.service.ts +++ b/frontend/src/services/job.service.ts @@ -172,6 +172,7 @@ type JobFileRequest = z.infer type FetchAllJobsResponse = z.infer type FetchJobsResponse = z.infer type FetchJobsByColumnResponse = z.infer +type KanbanChangesResponse = z.infer type FetchStatusValuesResponse = z.infer type CompanyDefaults = z.infer type PaginatedCompleteJobList = z.infer @@ -199,6 +200,10 @@ export const jobService = { }) }, + getKanbanChanges(after: string): Promise { + return api.getKanbanChanges({ queries: { after } }) + }, + getStatusChoices(): Promise { return Promise.resolve({ success: true, diff --git a/frontend/src/stores/__tests__/jobs.test.ts b/frontend/src/stores/__tests__/jobs.test.ts index 2369cf7c1..7a962b863 100644 --- a/frontend/src/stores/__tests__/jobs.test.ts +++ b/frontend/src/stores/__tests__/jobs.test.ts @@ -2,6 +2,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createPinia, setActivePinia } from 'pinia' import { useJobsStore } from '@/stores/jobs' +const { freshnessSubscribers } = vi.hoisted(() => ({ + freshnessSubscribers: new Map void | Promise>(), +})) + vi.mock('@/api/client', () => ({ api: {}, })) @@ -12,7 +16,10 @@ vi.mock('@/services/job.service', () => ({ vi.mock('@/composables/useDataFreshness', () => ({ dataFreshness: { - subscribe: vi.fn(), + subscribe: vi.fn((key: string, callback: () => void | Promise) => { + freshnessSubscribers.set(key, callback) + return vi.fn() + }), }, })) @@ -52,6 +59,7 @@ describe('jobs store kanban cache', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() + freshnessSubscribers.clear() }) it('normalizes column responses into jobs by id plus ordered column ids', async () => { @@ -96,6 +104,24 @@ describe('jobs store kanban cache', () => { expect(store.getKanbanColumnJobs('in_progress')?.[0]?.name).toBe('Updated') }) + it('clears the complete kanban cache when related display data changes', async () => { + const store = useJobsStore() + + await store.loadKanbanColumnWithCache('in_progress', async () => ({ + success: true, + jobs: [buildKanbanJob()], + total: 1, + filtered_count: 1, + has_more: false, + })) + + expect(freshnessSubscribers.has('kanban')).toBe(false) + await freshnessSubscribers.get('kanban_related')?.() + + expect(store.kanbanColumnCache).toEqual({}) + expect(store.kanbanJobs).toEqual({}) + }) + it('moves cached kanban job ids above or below a visible anchor', async () => { const store = useJobsStore() diff --git a/frontend/src/stores/jobs.ts b/frontend/src/stores/jobs.ts index d75b94a0f..5435678bd 100644 --- a/frontend/src/stores/jobs.ts +++ b/frontend/src/stores/jobs.ts @@ -52,7 +52,7 @@ export const useJobsStore = defineStore('jobs', () => { }, }) - dataFreshness.subscribe('kanban', () => { + dataFreshness.subscribe('kanban_related', () => { kanbanDatasetCache.invalidate() }) @@ -613,7 +613,6 @@ export const useJobsStore = defineStore('jobs', () => { currentContext, allKanbanJobs, - kanbanCacheGeneration: computed(() => kanbanDatasetCache.generation.value), getJobById, getKanbanJobById, getHeaderById, diff --git a/frontend/src/stores/timesheet.ts b/frontend/src/stores/timesheet.ts index 323b8c96f..4d9113d3e 100644 --- a/frontend/src/stores/timesheet.ts +++ b/frontend/src/stores/timesheet.ts @@ -427,7 +427,7 @@ export const useTimesheetStore = defineStore('timesheet', () => { const costLineData = { kind: 'time' as const, - desc: entryData.description, + desc: entryData.description || null, quantity: entryData.hours, unit_cost: entryData.wageRate, unit_rev: entryData.chargeOutRate, diff --git a/frontend/src/typed-router.d.ts b/frontend/src/typed-router.d.ts index 32e776607..b11d0a54e 100644 --- a/frontend/src/typed-router.d.ts +++ b/frontend/src/typed-router.d.ts @@ -20,9 +20,9 @@ import type { declare module 'vue-router' { interface TypesConfig { - ParamParsers: - | never + _ParamParsers: {} RouteNamedMap: import('vue-router/auto-routes').RouteNamedMap + _RouteFileInfoMap: import('vue-router/auto-routes')._RouteFileInfoMap } } @@ -329,240 +329,320 @@ declare module 'vue-router/auto-routes' { | '/[...path]' views: | never + pathParamNames: + | 'path' } 'src/pages/crm/calls.vue': { routes: | '/crm/calls' views: | never + pathParamNames: + | never } 'src/pages/crm/companies/(index).vue': { routes: | '/crm/companies/(index)' views: | never + pathParamNames: + | never } 'src/pages/crm/companies/[id].vue': { routes: | '/crm/companies/[id]' views: | never + pathParamNames: + | 'id' } 'src/pages/crm/people/(index).vue': { routes: | '/crm/people/(index)' views: | never + pathParamNames: + | never } 'src/pages/crm/people/[id].vue': { routes: | '/crm/people/[id]' views: | never + pathParamNames: + | 'id' } 'src/pages/jobs/[id]/(index).vue': { routes: | '/jobs/[id]/(index)' views: | never + pathParamNames: + | never } 'src/pages/jobs/[id]/workshop.vue': { routes: | '/jobs/[id]/workshop' views: | never + pathParamNames: + | never } 'src/pages/jobs/create.vue': { routes: | '/jobs/create' views: | never + pathParamNames: + | never } 'src/pages/kanban.vue': { routes: | '/kanban' views: | never + pathParamNames: + | never } 'src/pages/login.vue': { routes: | '/login' views: | never + pathParamNames: + | never } 'src/pages/purchasing/mappings.vue': { routes: | '/purchasing/mappings' views: | never + pathParamNames: + | never } 'src/pages/purchasing/po/(index).vue': { routes: | '/purchasing/po/(index)' views: | never + pathParamNames: + | never } 'src/pages/purchasing/po/[id].vue': { routes: | '/purchasing/po/[id]' views: | never + pathParamNames: + | 'id' } 'src/pages/purchasing/po/create.vue': { routes: | '/purchasing/po/create' views: | never + pathParamNames: + | never } 'src/pages/purchasing/po/create-from-quote.vue': { routes: | '/purchasing/po/create-from-quote' views: | never + pathParamNames: + | never } 'src/pages/purchasing/pricing.vue': { routes: | '/purchasing/pricing' views: | never + pathParamNames: + | never } 'src/pages/purchasing/stock.vue': { routes: | '/purchasing/stock' views: | never + pathParamNames: + | never } 'src/pages/quoting/chat.vue': { routes: | '/quoting/chat' views: | never + pathParamNames: + | never } 'src/pages/reports/data-quality/archived-jobs.vue': { routes: | '/reports/data-quality/archived-jobs' views: | never + pathParamNames: + | never } 'src/pages/reports/data-quality/duplicate-identities.vue': { routes: | '/reports/data-quality/duplicate-identities' views: | never + pathParamNames: + | never } 'src/pages/reports/data-quality/duplicate-phones.vue': { routes: | '/reports/data-quality/duplicate-phones' views: | never + pathParamNames: + | never } 'src/pages/reports/job-aging.vue': { routes: | '/reports/job-aging' views: | never + pathParamNames: + | never } 'src/pages/reports/job-movement.vue': { routes: | '/reports/job-movement' views: | never + pathParamNames: + | never } 'src/pages/reports/job-profitability.vue': { routes: | '/reports/job-profitability' views: | never + pathParamNames: + | never } 'src/pages/reports/kpi.vue': { routes: | '/reports/kpi' views: | never + pathParamNames: + | never } 'src/pages/reports/payroll-reconciliation.vue': { routes: | '/reports/payroll-reconciliation' views: | never + pathParamNames: + | never } 'src/pages/reports/profit-and-loss.vue': { routes: | '/reports/profit-and-loss' views: | never + pathParamNames: + | never } 'src/pages/reports/rdti-spend.vue': { routes: | '/reports/rdti-spend' views: | never + pathParamNames: + | never } 'src/pages/reports/sales-forecast.vue': { routes: | '/reports/sales-forecast' views: | never + pathParamNames: + | never } 'src/pages/reports/sales-pipeline.vue': { routes: | '/reports/sales-pipeline' views: | never + pathParamNames: + | never } 'src/pages/reports/staff-performance.vue': { routes: | '/reports/staff-performance' views: | never + pathParamNames: + | never } 'src/pages/reports/wip.vue': { routes: | '/reports/wip' views: | never + pathParamNames: + | never } 'src/pages/schedule.vue': { routes: | '/schedule' views: | never + pathParamNames: + | never } 'src/pages/session-check.vue': { routes: | '/session-check' views: | never + pathParamNames: + | never } 'src/pages/timesheets/daily.vue': { routes: | '/timesheets/daily' views: | never + pathParamNames: + | never } 'src/pages/timesheets/entry.vue': { routes: | '/timesheets/entry' views: | never + pathParamNames: + | never } 'src/pages/timesheets/my-time.vue': { routes: | '/timesheets/my-time' views: | never + pathParamNames: + | never } 'src/pages/timesheets/weekly.vue': { routes: | '/timesheets/weekly' views: | never + pathParamNames: + | never } 'src/pages/xero.vue': { routes: | '/xero' views: | never + pathParamNames: + | never } } diff --git a/frontend/src/types/processDocument.types.ts b/frontend/src/types/processDocument.types.ts index fe0e32f46..efcb98bf6 100644 --- a/frontend/src/types/processDocument.types.ts +++ b/frontend/src/types/processDocument.types.ts @@ -98,13 +98,7 @@ export interface WizardState { } export type WizardStep = - | 'loading' - | 'description' - | 'tasks' - | 'hazards' - | 'controls' - | 'ppe' - | 'review' + 'loading' | 'description' | 'tasks' | 'hazards' | 'controls' | 'ppe' | 'review' export const WIZARD_STEPS: WizardStep[] = [ 'description', diff --git a/frontend/src/utils/__tests__/authConsoleErrors.test.ts b/frontend/src/utils/__tests__/authConsoleErrors.test.ts index 3d9599961..89d9c5377 100644 --- a/frontend/src/utils/__tests__/authConsoleErrors.test.ts +++ b/frontend/src/utils/__tests__/authConsoleErrors.test.ts @@ -117,6 +117,54 @@ describe('auth E2E console allowance', () => { ).toBe(false) }) + it.each([ + { pathname: '/api/process/categories/', method: 'GET' }, + { pathname: '/api/job/jobs/fetch-by-column/draft/', method: 'GET' }, + { pathname: '/api/accounts/staff/all/', method: 'GET' }, + { pathname: '/api/session-replays/recordings/', method: 'POST' }, + ])('does not consume a 401 from $pathname during login', ({ pathname, method }) => { + const allowance = createLoginSessionCheckConsoleAllowance(() => 1000) + const stop = allowance.startLoginWindow() + + allowance.recordResponse({ pathname, method, status: 401 }) + stop() + + expect(allowance.consumeIfExpected(console401(1000))).toBe(false) + }) + + it('never consumes a rejected login, even inside the login window', () => { + // A 401 from the token endpoint means the credentials were refused. That is + // the answer, not a symptom, and must always fail the suite. + const allowance = createLoginSessionCheckConsoleAllowance(() => 1000) + const stop = allowance.startLoginWindow() + + allowance.recordResponse({ + pathname: '/api/accounts/token/', + method: 'POST', + status: 401, + }) + allowance.recordResponse({ + pathname: '/api/accounts/token/refresh/', + method: 'POST', + status: 401, + }) + stop() + + expect(allowance.consumeIfExpected(console401(1000))).toBe(false) + }) + + it('does not consume a burst that lands outside any login window', () => { + const allowance = createLoginSessionCheckConsoleAllowance(() => 1000) + + allowance.recordResponse({ + pathname: '/api/process/categories/', + method: 'GET', + status: 401, + }) + + expect(allowance.consumeIfExpected(console401(1000))).toBe(false) + }) + it('does not consume page errors with the same text', () => { const allowance = createLoginSessionCheckConsoleAllowance(() => 1000) const stop = allowance.startLoginWindow() diff --git a/frontend/src/utils/metalType.ts b/frontend/src/utils/metalType.ts index 4c71657fe..8db5c1f65 100644 --- a/frontend/src/utils/metalType.ts +++ b/frontend/src/utils/metalType.ts @@ -14,7 +14,6 @@ export const metalTypeOptions: Array<{ value: MetalType; label: string }> = [ { value: 'titanium', label: 'Titanium' }, { value: 'zinc', label: 'Zinc' }, { value: 'galvanized', label: 'Galvanized' }, - { value: 'unspecified', label: 'Unspecified' }, { value: 'other', label: 'Other' }, ] diff --git a/frontend/src/views/AdminStaffView.vue b/frontend/src/views/AdminStaffView.vue index bf4393e74..4276f5d57 100644 --- a/frontend/src/views/AdminStaffView.vue +++ b/frontend/src/views/AdminStaffView.vue @@ -43,7 +43,6 @@ Last Name Is Office Staff Is SuperUserLast Login Date Joined Actions
    ✔️ {{ formatDateTime(staff.last_login) }} {{ formatDateTime(staff.date_joined) }}
    + No staff found for the current search criteria.