{"server":"io.github.danafitkowski/cpp-cpm-engine","count":5,"changes":[{"tool":"collapsed_as_built","kind":"changed","observed_at":"2026-09-28T16:11:10.248Z","fields":["description"],"description_before":"Collapsed As-Built / But-For analysis on a post-impact XER.\n\n            Implements AACE RP 29R-03 §3.8 Modeled / Subtractive / Single\n            Base method (paired with MIP 3.3 Windows for the dual-method\n            gap report per SCL §11.5). Validates a forensic windows\n            analysis (MIP 3.3) by independently computing the same\n            project drift via subtractive removal of delays from the\n            as-built schedule.\n\n            For each delay event, the as-built duration of every\n            ``affected_activity`` is shortened by ``impact_days`` (or\n            removed entirely if ``removal_method=\"remove\"``), then CPM\n            re-runs and the resulting \"but-for\" finish date is compared\n            to the as-built finish. Cumulative pass removes ALL events at\n            once for a project-level but-for finish.\n\n            Use this tool when opposing counsel demands a but-for analysis\n            or you need a dual-method validation pairing §3.3 (windows) with\n            §3.8 (collapsed-as-built). For prospective fragnet insertion\n            (MIP 3.7), use ``time_impact_analysis_fragnet`` instead.\n\n            Args:\n                as_built_xer_path:    server-side post-impact XER (after delays incurred).\n                as_built_xer_content: full text of post-impact XER (alternative for hosted/remote use).\n                                      Supply EXACTLY ONE of path/content.\n                delay_events: list of event dicts. Each must have\n                    ``event_id``, ``affected_activities`` (list of\n                    task_codes), and ``impact_days`` (number). Optional:\n                    ``removal_method`` ('shorten'|'remove'),\n                    ``responsible_party``, ``name``, ``description``.\n                output_dir: optional output dir for HTML/CSV (tempdir if \"\").\n                project_name: optional override.\n                removal_method: global default 'shorten' or 'remove'.\n                contractor_filter: when True, exclude contractor-caused\n                    events from the cumulative pass (owner audit mode).\n\n            Returns:\n                {\n                  \"as_built_finish\": \"YYYY-MM-DD\",\n                  \"per_event_results\": [{event_id, but_for_finish,\n                                          impact_days_collapsed,\n                                          duration_removal_basis,\n                                          finish_driver_after_removal, ...}, ...],\n                  # duration_removal_basis discloses WHAT duration was removed\n                  # and on what basis; finish_driver_after_removal discloses\n                  # WHAT drives the but-for finish (incl. whether it is bound by\n                  # the data-date floor) so a reader sees WHY the finish did or\n                  # did not move across data dates.\n                  \"cumulative_but_for_finish\": \"YYYY-MM-DD\",\n                  \"cumulative_impact_days\": int,\n                  \"dual_method_gap\": dict | None,\n                  \"output_files\": {...},\n                  \"warnings\": [...],\n                  \"method\": \"AACE 29R-03 §3.8 (Modeled/Subtractive/Single Simulation)\"\n                }\n            ","description_after":"Collapsed As-Built / But-For analysis on a post-impact XER.\n\n            Implements AACE RP 29R-03 §3.8 Modeled / Subtractive / Single\n            Base method (paired with MIP 3.3 Windows for the dual-method\n            gap report per SCL §11.5). Validates a forensic windows\n            analysis (MIP 3.3) by independently computing the same\n            project drift via subtractive removal of delays from the\n            as-built schedule.\n\n            For each delay event, the as-built duration of every\n            ``affected_activity`` is shortened by ``impact_days`` working\n            days on that activity's calendar (or removed entirely if\n            ``removal_method=\"remove\"``), then CPM re-runs and the\n            resulting \"but-for\" finish date is compared to the as-built\n            finish. Cumulative pass removes ALL events at once for a\n            project-level but-for finish. Every finish shift is stated in\n            working days on ``workday_calendar``; the calendar-day span of\n            the same shift is kept beside it, and ``day_unit_basis`` names\n            the unit of every bare ``*_days`` key in the result.\n\n            Use this tool when opposing counsel demands a but-for analysis\n            or you need a dual-method validation pairing §3.3 (windows) with\n            §3.8 (collapsed-as-built). For prospective fragnet insertion\n            (MIP 3.7), use ``time_impact_analysis_fragnet`` instead.\n\n            Args:\n                as_built_xer_path:    server-side post-impact XER (after delays incurred).\n                as_built_xer_content: full text of post-impact XER (alternative for hosted/remote use).\n                                      Supply EXACTLY ONE of path/content.\n                delay_events: list of event dicts. Each must have\n                    ``event_id``, ``affected_activities`` (list of\n                    task_codes), and ``impact_days`` (number of working\n                    days on the affected activity's calendar). Optional:\n                    ``removal_method`` ('shorten'|'remove'),\n                    ``responsible_party``, ``name``, ``description``.\n                output_dir: optional output dir for HTML/CSV (tempdir if \"\").\n                project_name: optional override.\n                removal_method: global default 'shorten' or 'remove'.\n                contractor_filter: when True, exclude contractor-caused\n                    events from the cumulative pass (owner audit mode).\n\n            Returns:\n                {\n                  \"as_built_finish\": \"YYYY-MM-DD\",\n                  \"per_event_results\": [{event_id, but_for_finish,\n                                          impact_workdays_collapsed,\n                                          collapse_state,\n                                          duration_removal_basis,\n                                          finish_driver_after_removal, ...}, ...],\n                  # impact_workdays_collapsed is the event's finish shift in\n                  # working days on workday_calendar. Each event also carries\n                  # impact_days_collapsed: the same shift in calendar days.\n                  # duration_removal_basis discloses WHAT duration was removed\n                  # and on what basis; finish_driver_after_removal discloses\n                  # WHAT drives the but-for finish (incl. whether it is bound by\n                  # the data-date floor) so a reader sees WHY the finish did or\n                  # did not move across data dates.\n                  \"cumulative_but_for_finish\": \"YYYY-MM-DD\",\n                  \"cumulative_impact_workdays\": int,  # working days on workday_calendar\n                  \"cumulative_impact_days\": int,  # the same shift in calendar days\n                  \"cumulative_collapse_state\": str,  # a 'locked_by_actuals' zero is not \"no impact\"\n                  \"workday_calendar\": str,  # the calendar every working-day figure is stated on\n                  \"day_unit_basis\": dict,  # each ba","description_similarity":0.859,"before_hash":"92c8488b290a2acb8d3c16b19a9b523d","after_hash":"4e43d607efcedf90d5609f1a08733b27"},{"tool":"forensic_windows_analysis","kind":"changed","observed_at":"2026-09-28T10:13:10.682Z","fields":["description"],"description_before":"Run forensic windows analysis (AACE RP 29R-03 §3.3, MIP 3.3\n            Observational / Dynamic / Contemporaneous As-Is) across\n            multiple Primavera P6 XER snapshots and return the full analysis\n            dict.\n\n            This is the headline forensic tool — it computes per-window\n            completion shifts, per-window slip registers (per-activity slip\n            with critical/non-critical flag), per-window duration growth on\n            critical-path activities, per-window per-party attribution\n            (Owner / Contractor / Concurrent / Force Majeure / Unattributed),\n            and cumulative project drift from baseline. The attribution math\n            satisfies the CPP conservation check, per the AACE 29R-03\n            §3.3.E.13 requirement that the summed per-period net impacts\n            equal the difference between the first schedule update and the\n            last schedule update used in the evaluation (per-party day\n            buckets sum to project drift within ±1 day, no cascade-double-\n            counting).\n\n            Use this tool for the full multi-window forensic claim. If you\n            already have a windows result and only want the per-window ×\n            per-party grid view, call ``concurrent_delay_matrix`` instead.\n\n            Args:\n                schedules: list of dicts in chronological order. Minimum 2\n                    entries (baseline + at least one update). Each dict\n                    must contain ``label`` (str) and EXACTLY ONE of:\n                      - ``xer_path``    — server-side filesystem path, OR\n                      - ``xer_content`` — full XER text content.\n                    Use ``xer_content`` when calling a hosted MCP server\n                    from a remote client whose XER lives locally.\n                project_name: optional override; auto-picked from XER if \"\".\n                baseline_idx: which entry in ``schedules`` is the contract\n                    baseline (default 0 = first one).\n                entitlement_milestone: optional task_code (e.g.\n                    \"Ready for Takeover\") — recorded on the result, not used\n                    for math.\n                output_dir: optional dir for HTML dashboard / DOCX report.\n                    If \"\", a tempdir is used and dropped after — the\n                    dashboard / report paths in the response will point to\n                    the temp location (caller responsible for moving them).\n\n            Returns:\n                {\n                  \"analysis\": full dict from run_windows() with keys:\n                    \"windows\", \"cumulative\", \"baseline_label\", \"data_dates\",\n                    \"attribution_summary\", \"mcpm_attribution\", ...,\n                  \"dashboard\": path to HTML dashboard (server-side),\n                  \"report\":    path to DOCX executive report (server-side),\n                  \"baseline_stability\": {\"worst_severity\", \"has_block\", ...},\n                  \"data_date_corrections\": the record of each update whose\n                      data date was moved to its latest actual, and of a\n                      baseline with actual dates after its data date\n                      ([] on clean files); each is also a data-quality\n                      warning\n                }\n\n                On failure: {\"error\": \"...\"} with no schedules processed.\n            ","description_after":"Run forensic windows analysis (AACE RP 29R-03 §3.3, MIP 3.3\n            Observational / Dynamic / Contemporaneous As-Is) across\n            multiple Primavera P6 XER snapshots and return the full analysis\n            dict.\n\n            This is the headline forensic tool — it computes per-window\n            completion shifts, per-window slip registers (per-activity slip\n            with a critical flag (total float of zero or less, or the driving\n            path in a schedule with negative float)), per-window duration\n            growth on critical-path activities, per-window per-party attribution\n            (Owner / Contractor / Concurrent / Force Majeure / Unattributed),\n            and cumulative project drift from baseline. The attribution math\n            satisfies the CPP conservation check, per the AACE 29R-03\n            §3.3.E.13 requirement that the summed per-period net impacts\n            equal the difference between the first schedule update and the\n            last schedule update used in the evaluation (per-party day\n            buckets sum to project drift within ±1 day, no cascade-double-\n            counting).\n\n            Use this tool for the full multi-window forensic claim. If you\n            already have a windows result and only want the per-window ×\n            per-party grid view, call ``concurrent_delay_matrix`` instead.\n\n            Args:\n                schedules: list of dicts in chronological order. Minimum 2\n                    entries (baseline + at least one update). Each dict\n                    must contain ``label`` (str) and EXACTLY ONE of:\n                      - ``xer_path``    — server-side filesystem path, OR\n                      - ``xer_content`` — full XER text content.\n                    Use ``xer_content`` when calling a hosted MCP server\n                    from a remote client whose XER lives locally.\n                project_name: optional override; auto-picked from XER if \"\".\n                baseline_idx: which entry in ``schedules`` is the contract\n                    baseline (default 0 = first one).\n                entitlement_milestone: optional task_code (e.g.\n                    \"Ready for Takeover\") — recorded on the result, not used\n                    for math.\n                output_dir: optional dir for HTML dashboard / DOCX report.\n                    If \"\", a tempdir is used and dropped after — the\n                    dashboard / report paths in the response will point to\n                    the temp location (caller responsible for moving them).\n\n            Returns:\n                {\n                  \"analysis\": full dict from run_windows() with keys:\n                    \"windows\", \"cumulative\", \"baseline_label\", \"data_dates\",\n                    \"attribution_summary\", \"mcpm_attribution\", ...,\n                  \"dashboard\": path to HTML dashboard (server-side),\n                  \"report\":    path to DOCX executive report (server-side),\n                  \"baseline_stability\": {\"worst_severity\", \"has_block\", ...},\n                  \"data_date_corrections\": the record of each update whose\n                      data date was moved to its latest actual, and of a\n                      baseline with actual dates after its data date\n                      ([] on clean files); each is also a data-quality\n                      warning\n                  \"analysis\".\"criticality_basis\": the schedules read by\n                      their driving path because they have negative float,\n                      and those beside them ([] when no schedule has\n                      negative float); the box they write is one\n                      data-quality warning\n                }\n\n                On failure: {\"error\": \"...\"} with no schedules processed.\n            ","description_similarity":0.921,"before_hash":"b827a0207d728159a21f1d9cffcef977","after_hash":"d1865b236a76f14b78dd6c6605d5199a"},{"tool":"slip_velocity","kind":"changed","observed_at":"2026-09-27T16:16:49.325Z","fields":["description"],"description_before":"Per-window slip velocity & acceleration trend across XER snapshots.\n\n            Computes three signed metrics per window from the underlying\n            forensic windows analysis:\n              - slip_velocity_days_per_day: completion shift / window\n                duration (positive = slipping, negative = recovering).\n                Numerator is the WORKING-day completion shift. The\n                denominator is WORKING days between the prior and later\n                data dates on the same calendar\n                (``window_duration_workdays``), making this a same-day-type\n                working-day/working-day rate. It falls back to CALENDAR days\n                only for legacy window dicts that predate that field, and\n                such a row is flagged ``velocity_basis=\"wd/cd\"``. Read\n                ``velocity_basis`` to know which denominator produced the\n                figure.\n\n                Each velocity field name states the ratio it holds:\n                ``slip_velocity_workdays_per_workday`` (populated only on\n                the wd/wd path), ``slip_velocity_workdays_per_calendar_day``\n                (working-days of slip per CALENDAR day elapsed, computed\n                against ``window_duration_days``), and\n                ``slip_velocity_days_per_day`` as the retained back-compat\n                name for whichever basis was selected. Quote ``basis`` in\n                any expert report.\n\n                NOTE (2026-09-03): the two named fields are no longer equal.\n                ``slip_velocity_workdays_per_calendar_day`` used to be a\n                blind copy of the headline velocity, which made its name\n                wrong once the denominator moved to working days — it read\n                5/10 = 0.500 while its name promised 5/14 = 0.357. It now\n                holds the calendar-day rate it is named for.\n              - slip_acceleration: velocity[n] - velocity[n-1] (positive\n                = slip rate increasing, negative = decelerating/recovery)\n              - half_period_estimated_slip_days: shift / 2 (forensic\n                \"where were we at the midpoint\" centroid estimate), in\n                WORKING days\n\n            Cumulative aggregates ``mean_velocity_days_per_day`` plus a\n            mean per basis — ``mean_velocity_workdays_per_workday`` and\n            ``mean_velocity_workdays_per_calendar_day`` — each computed\n            only from the rows that actually carry that denominator, so a\n            mean is never labelled with a basis it did not use (None when\n            no window carried it). ``velocity_basis_set`` lists the bases\n            present and ``velocity_units`` describes them, including an\n            explicit MIXED string when a run spans both.\n            Also ``max_velocity_window`` and accelerating / decelerating /\n            recovery window counts.\n\n            Honest caveats embedded in the response (mandatory for expert\n            reports): midpoint estimates are probabilistic centroids, not\n            observed events; velocity is per-window average, not\n            instantaneous; acceleration is a finite difference, not a true\n            second derivative.\n\n            Built on top of AACE RP 29R-03 §3.3 windows analysis. Use this\n            tool when you want a slip-rate trend line on top of the same\n            per-window math ``forensic_windows_analysis`` already computes.\n\n            Args:\n                schedules: chronologically ordered list of dicts — the SAME\n                    shape ``forensic_windows_analysis`` accepts. Each dict\n                    carries ``label`` (optional) and EXACTLY ONE of\n                    ``xer_content`` or ``xer_path``. Preferred input for\n                    hosted/remote clients.\n                xer_paths: legacy chronologically ordered list of server-side\n                    XER paths.\n                xer_contents: legacy chronologically ordered list of XER text\n                    contents (alternative for ho","description_after":"Per-window slip velocity & acceleration trend across XER snapshots.\n\n            Computes three signed metrics per window from the underlying\n            forensic windows analysis:\n              - slip_velocity_days_per_day: completion shift / window\n                duration (positive = slipping, negative = recovering).\n                Numerator is the WORKING-day completion shift. The\n                denominator is WORKING days between the prior and later\n                data dates on the same calendar\n                (``window_duration_workdays``), making this a same-day-type\n                working-day/working-day rate. It falls back to CALENDAR days\n                only for legacy window dicts that predate that field, and\n                such a row is flagged ``velocity_basis=\"wd/cd\"``. Read\n                ``velocity_basis`` to know which denominator produced the\n                figure.\n\n                Each velocity field name states the ratio it holds:\n                ``slip_velocity_workdays_per_workday`` (populated only on\n                the wd/wd path), ``slip_velocity_workdays_per_calendar_day``\n                (working-days of slip per CALENDAR day elapsed, computed\n                against ``window_duration_days``), and\n                ``slip_velocity_days_per_day`` as the retained back-compat\n                name for whichever basis was selected. Quote ``basis`` in\n                any expert report.\n\n                NOTE (2026-09-03): the two named fields are no longer equal.\n                ``slip_velocity_workdays_per_calendar_day`` used to be a\n                blind copy of the headline velocity, which made its name\n                wrong once the denominator moved to working days — it read\n                5/10 = 0.500 while its name promised 5/14 = 0.357. It now\n                holds the calendar-day rate it is named for.\n              - slip_acceleration: velocity[n] - velocity[n-1] (positive\n                = slip rate increasing, negative = decelerating/recovery)\n              - half_period_estimated_slip_days: shift / 2 (forensic\n                \"where were we at the midpoint\" centroid estimate), in\n                WORKING days\n\n            Cumulative aggregates ``mean_velocity_days_per_day`` plus a\n            mean per basis — ``mean_velocity_workdays_per_workday`` and\n            ``mean_velocity_workdays_per_calendar_day`` — each computed\n            only from the rows that actually carry that denominator, so a\n            mean is never labelled with a basis it did not use (None when\n            no window carried it). ``velocity_basis_set`` lists the bases\n            present and ``velocity_units`` describes them, including an\n            explicit MIXED string when a run spans both.\n            Also ``max_velocity_window`` and accelerating / decelerating /\n            recovery window counts.\n\n            Honest caveats embedded in the response (mandatory for expert\n            reports): midpoint estimates are probabilistic centroids, not\n            observed events; velocity is per-window average, not\n            instantaneous; acceleration is a finite difference, not a true\n            second derivative.\n\n            Built on top of AACE RP 29R-03 §3.3 windows analysis. Use this\n            tool when you want a slip-rate trend line on top of the same\n            per-window math ``forensic_windows_analysis`` already computes.\n\n            Args:\n                schedules: chronologically ordered list of dicts — the SAME\n                    shape ``forensic_windows_analysis`` accepts. Each dict\n                    carries ``label`` (optional) and EXACTLY ONE of\n                    ``xer_content`` or ``xer_path``. Preferred input for\n                    hosted/remote clients.\n                xer_paths: legacy chronologically ordered list of server-side\n                    XER paths.\n                xer_contents: legacy chronologically ordered list of XER text\n                    contents (alternative for ho","description_similarity":0.953,"before_hash":"bebf3d2523aa014b26512eb6072e1958","after_hash":"9fa95a3fcb11eb9d2e0960fd96deecbc"},{"tool":"concurrent_delay_matrix","kind":"changed","observed_at":"2026-09-27T16:16:49.325Z","fields":["description"],"description_before":"Build the per-window x per-party concurrent-delay attribution\n            matrix from a chronological list of XER snapshots.\n\n            Implements the per-window concurrency view per AACE RP 29R-03\n            §3.3.I (apportionment) and §4.2 (concurrency). Where\n            ``forensic_windows_analysis`` answers \"how many days does each\n            party own across the whole project?\", this tool answers \"how did\n            each window distribute its shift across the parties?\" — useful\n            when defending or attacking concurrency findings on a\n            window-by-window basis.\n\n            CPP conservation check, per the AACE 29R-03 §3.3.E.13\n            requirement that the summed per-period net impacts equal the\n            difference between the first schedule update and the last\n            schedule update used in the evaluation: the sum of per-party\n            column totals equals the sum of per-window completion shifts\n            within ±1 day of rounding. The column-total definition is this\n            tool's own bookkeeping, not an AACE rule. The ``conservation_check`` field on\n            the response reflects this; ``conservation_diff_days`` carries\n            the exact gap.\n\n            IMPORTANT — conservation is NOT attribution. ``conservation_check``\n            can be True (the columns sum to the grand total) even when 100% of\n            the shift lands in the Unattributed column, i.e. no party owns any\n            of the drift. Read ``unattributed_share_pct`` and\n            ``high_unattributed_share_warning`` to know whether a meaningful\n            apportionment actually occurred. A fully-unattributed matrix\n            conserves perfectly but attributes nothing — never present its\n            green conservation check as a validated apportionment.\n\n            Use this tool when you only need the matrix view; use\n            ``forensic_windows_analysis`` for the full claim.\n\n            Args:\n                schedules: chronologically ordered list of dicts — the SAME\n                    shape ``forensic_windows_analysis`` accepts. Each dict\n                    carries ``label`` (optional) and EXACTLY ONE of\n                    ``xer_content`` (full XER text, hosted/remote use) or\n                    ``xer_path`` (server-side path, local use). This is the\n                    preferred input for hosted/remote clients.\n                xer_paths: legacy chronologically ordered list of server-side\n                    XER file paths (local-server use).\n                xer_contents: legacy chronologically ordered list of XER text\n                    contents. Each element is the full text of one XER.\n                Supply EXACTLY ONE of schedules / xer_paths / xer_contents\n                (lists must have at least 2 entries either way).\n\n            Returns:\n                {\n                  \"parties\": [\"Owner\", \"Contractor\", \"Concurrent\",\n                              \"Force Majeure\", \"Unattributed\"],\n                  # Unit for every shift_* field and the grand totals. Always\n                  # \"working_days\" — the matrix measures the completion shift\n                  # in working days (Dana default). The *_calendar_days twins\n                  # express the SAME shift in calendar days so an unlabeled\n                  # \"11\" can never be mistaken for the 15-calendar-day value.\n                  \"shift_unit\": \"working_days\",\n                  \"rows\": [{ \"window_label\", \"period_start\", \"period_end\",\n                             # shift_days == shift_workdays (working days,\n                             # legacy alias). shift_calendar_days is the same\n                             # shift in calendar days; shift_basis names the\n                             # finish driver the shift was measured on.\n                             \"shift_days\", \"shift_unit\", \"shift_workdays\",\n                             \"shift_calendar_days\", \"shift_basis\",\n                             \"parties\": {party: days},\n      ","description_after":"Build the per-window x per-party concurrent-delay attribution\n            matrix from a chronological list of XER snapshots.\n\n            Implements the per-window concurrency view per AACE RP 29R-03\n            §3.3.I (apportionment) and §4.2 (concurrency). Where\n            ``forensic_windows_analysis`` answers \"how many days does each\n            party own across the whole project?\", this tool answers \"how did\n            each window distribute its shift across the parties?\" — useful\n            when defending or attacking concurrency findings on a\n            window-by-window basis.\n\n            CPP conservation check, per the AACE 29R-03 §3.3.E.13\n            requirement that the summed per-period net impacts equal the\n            difference between the first schedule update and the last\n            schedule update used in the evaluation: the sum of per-party\n            column totals equals the sum of per-window completion shifts\n            within ±1 day of rounding. The column-total definition is this\n            tool's own bookkeeping, not an AACE rule. The ``conservation_check`` field on\n            the response reflects this; ``conservation_diff_days`` carries\n            the exact gap.\n\n            IMPORTANT — conservation is NOT attribution. ``conservation_check``\n            can be True (the columns sum to the grand total) even when 100% of\n            the shift lands in the Unattributed column, i.e. no party owns any\n            of the drift. Read ``unattributed_share_pct`` and\n            ``high_unattributed_share_warning`` to know whether a meaningful\n            apportionment actually occurred. A fully-unattributed matrix\n            conserves perfectly but attributes nothing — never present its\n            green conservation check as a validated apportionment.\n\n            Use this tool when you only need the matrix view; use\n            ``forensic_windows_analysis`` for the full claim.\n\n            Args:\n                schedules: chronologically ordered list of dicts — the SAME\n                    shape ``forensic_windows_analysis`` accepts. Each dict\n                    carries ``label`` (optional) and EXACTLY ONE of\n                    ``xer_content`` (full XER text, hosted/remote use) or\n                    ``xer_path`` (server-side path, local use). This is the\n                    preferred input for hosted/remote clients.\n                xer_paths: legacy chronologically ordered list of server-side\n                    XER file paths (local-server use).\n                xer_contents: legacy chronologically ordered list of XER text\n                    contents. Each element is the full text of one XER.\n                Supply EXACTLY ONE of schedules / xer_paths / xer_contents\n                (lists must have at least 2 entries either way).\n\n            Returns:\n                {\n                  \"parties\": [\"Owner\", \"Contractor\", \"Concurrent\",\n                              \"Force Majeure\", \"Unattributed\"],\n                  # Unit for every shift_* field and the grand totals. Always\n                  # \"working_days\" — the matrix measures the completion shift\n                  # in working days (Dana default). The *_calendar_days twins\n                  # express the SAME shift in calendar days so an unlabeled\n                  # \"11\" can never be mistaken for the 15-calendar-day value.\n                  \"shift_unit\": \"working_days\",\n                  \"rows\": [{ \"window_label\", \"period_start\", \"period_end\",\n                             # shift_days == shift_workdays (working days,\n                             # legacy alias). shift_calendar_days is the same\n                             # shift in calendar days; shift_basis names the\n                             # finish driver the shift was measured on.\n                             \"shift_days\", \"shift_unit\", \"shift_workdays\",\n                             \"shift_calendar_days\", \"shift_basis\",\n                             \"parties\": {party: days},\n      ","description_similarity":0.936,"before_hash":"bb6029944fdbea20c175e1e628e6b460","after_hash":"b5d6291235ff9d583f2c8b2bb6246a6f"},{"tool":"forensic_windows_analysis","kind":"changed","observed_at":"2026-09-27T16:16:49.325Z","fields":["description"],"description_before":"Run forensic windows analysis (AACE RP 29R-03 §3.3, MIP 3.3\n            Observational / Dynamic / Contemporaneous As-Is) across\n            multiple Primavera P6 XER snapshots and return the full analysis\n            dict.\n\n            This is the headline forensic tool — it computes per-window\n            completion shifts, per-window slip registers (per-activity slip\n            with critical/non-critical flag), per-window duration growth on\n            critical-path activities, per-window per-party attribution\n            (Owner / Contractor / Concurrent / Force Majeure / Unattributed),\n            and cumulative project drift from baseline. The attribution math\n            satisfies the CPP conservation check, per the AACE 29R-03\n            §3.3.E.13 requirement that the summed per-period net impacts\n            equal the difference between the first schedule update and the\n            last schedule update used in the evaluation (per-party day\n            buckets sum to project drift within ±1 day, no cascade-double-\n            counting).\n\n            Use this tool for the full multi-window forensic claim. If you\n            already have a windows result and only want the per-window ×\n            per-party grid view, call ``concurrent_delay_matrix`` instead.\n\n            Args:\n                schedules: list of dicts in chronological order. Minimum 2\n                    entries (baseline + at least one update). Each dict\n                    must contain ``label`` (str) and EXACTLY ONE of:\n                      - ``xer_path``    — server-side filesystem path, OR\n                      - ``xer_content`` — full XER text content.\n                    Use ``xer_content`` when calling a hosted MCP server\n                    from a remote client whose XER lives locally.\n                project_name: optional override; auto-picked from XER if \"\".\n                baseline_idx: which entry in ``schedules`` is the contract\n                    baseline (default 0 = first one).\n                entitlement_milestone: optional task_code (e.g.\n                    \"Ready for Takeover\") — recorded on the result, not used\n                    for math.\n                output_dir: optional dir for HTML dashboard / DOCX report.\n                    If \"\", a tempdir is used and dropped after — the\n                    dashboard / report paths in the response will point to\n                    the temp location (caller responsible for moving them).\n\n            Returns:\n                {\n                  \"analysis\": full dict from run_windows() with keys:\n                    \"windows\", \"cumulative\", \"baseline_label\", \"data_dates\",\n                    \"attribution_summary\", \"mcpm_attribution\", ...,\n                  \"dashboard\": path to HTML dashboard (server-side),\n                  \"report\":    path to DOCX executive report (server-side),\n                  \"baseline_stability\": {\"worst_severity\", \"has_block\", ...}\n                }\n\n                On failure: {\"error\": \"...\"} with no schedules processed.\n            ","description_after":"Run forensic windows analysis (AACE RP 29R-03 §3.3, MIP 3.3\n            Observational / Dynamic / Contemporaneous As-Is) across\n            multiple Primavera P6 XER snapshots and return the full analysis\n            dict.\n\n            This is the headline forensic tool — it computes per-window\n            completion shifts, per-window slip registers (per-activity slip\n            with critical/non-critical flag), per-window duration growth on\n            critical-path activities, per-window per-party attribution\n            (Owner / Contractor / Concurrent / Force Majeure / Unattributed),\n            and cumulative project drift from baseline. The attribution math\n            satisfies the CPP conservation check, per the AACE 29R-03\n            §3.3.E.13 requirement that the summed per-period net impacts\n            equal the difference between the first schedule update and the\n            last schedule update used in the evaluation (per-party day\n            buckets sum to project drift within ±1 day, no cascade-double-\n            counting).\n\n            Use this tool for the full multi-window forensic claim. If you\n            already have a windows result and only want the per-window ×\n            per-party grid view, call ``concurrent_delay_matrix`` instead.\n\n            Args:\n                schedules: list of dicts in chronological order. Minimum 2\n                    entries (baseline + at least one update). Each dict\n                    must contain ``label`` (str) and EXACTLY ONE of:\n                      - ``xer_path``    — server-side filesystem path, OR\n                      - ``xer_content`` — full XER text content.\n                    Use ``xer_content`` when calling a hosted MCP server\n                    from a remote client whose XER lives locally.\n                project_name: optional override; auto-picked from XER if \"\".\n                baseline_idx: which entry in ``schedules`` is the contract\n                    baseline (default 0 = first one).\n                entitlement_milestone: optional task_code (e.g.\n                    \"Ready for Takeover\") — recorded on the result, not used\n                    for math.\n                output_dir: optional dir for HTML dashboard / DOCX report.\n                    If \"\", a tempdir is used and dropped after — the\n                    dashboard / report paths in the response will point to\n                    the temp location (caller responsible for moving them).\n\n            Returns:\n                {\n                  \"analysis\": full dict from run_windows() with keys:\n                    \"windows\", \"cumulative\", \"baseline_label\", \"data_dates\",\n                    \"attribution_summary\", \"mcpm_attribution\", ...,\n                  \"dashboard\": path to HTML dashboard (server-side),\n                  \"report\":    path to DOCX executive report (server-side),\n                  \"baseline_stability\": {\"worst_severity\", \"has_block\", ...},\n                  \"data_date_corrections\": the record of each update whose\n                      data date was moved to its latest actual, and of a\n                      baseline with actual dates after its data date\n                      ([] on clean files); each is also a data-quality\n                      warning\n                }\n\n                On failure: {\"error\": \"...\"} with no schedules processed.\n            ","description_similarity":0.939,"before_hash":"19cdc83903bd91b8eb63d4cc270f0b49","after_hash":"b827a0207d728159a21f1d9cffcef977"}],"next_before":null,"next_actions":[]}