Overview#
The Analysis tab is the developer-facing inspection layer for a selected model. It combines:
- model summary information
- related model tables (
Inner Models,Previous Models,Cluster Models) - the
Archive Parameterspanel with time-depth, approach, direct targets, overcome state, reverse targets, and cancellation state - legacy forecast toggles that still live outside the React state tree
The current implementation is not a pure PHP template and not a pure standalone SPA. It is a hybrid runtime where React renders the visible UI, while the legacy page still provides the surrounding accordion shell, hidden bridge elements, chart integration, and toggle bindings.
Primary source files#
resources/react/init.jsx- React host,Analysisaccordion item, JSON fetch, related-model tables, archive parameter cardsresources/css/react-ui.css- visual styling for the ReactAnalysissectionresources/js/main.js- legacy DOM bridge that writes the selectedmodel_idinto the hidden host inside#collapseAnalysistemplates/private/analysis_ajax.php- JSON/HTML backend endpoint used by the tabinc_calc_controls.php- legacy business logic and constants that define the original control/aim semantics
Runtime architecture#
React host#
The visible tab content is rendered by AnalysisAccordionItem in resources/react/init.jsx.
It does not own model selection directly. Instead, it observes a hidden DOM node:
<span id="modelId" class="visually-hidden">0</span>
When that node changes, React starts a new request to:
templates/private/analysis_ajax.php?model_id=...&format=json
Legacy to React bridge#
The legacy layer in resources/js/main.js remains responsible for pushing the selected model id into the hidden bridge element inside #collapseAnalysis.
The key helper is:
mpSetAnalysisModelIdState(modelId)
Important detail: the bridge payload is intentionally numeric only. The React host treats the node as a pure model_id state carrier and no longer expects formatted text there.
Server endpoint#
templates/private/analysis_ajax.php is the data backend for the tab.
In modern runtime it mainly serves JSON, but it still keeps compatibility with legacy HTML output paths. When working on the tab, the JSON path is the primary production path.
The endpoint now has a stricter split of responsibilities:
- the JSON branch keeps raw API payload semantics for React
- the legacy HTML branch applies local escaping for DB-backed text output without altering the JSON contract
Request flow#
- The user selects or opens a model in the legacy UI.
main.jscallsmpSetAnalysisModelIdState(modelId).- The hidden
#collapseAnalysis #modelIdnode changes. - The React
MutationObserverinAnalysisAccordionItemdetects the change. - React aborts any stale in-flight request, keeps the latest pending
model_id, and fetches a fresh JSON payload fromanalysis_ajax.php. - The payload is rendered into model summary, related-model tables, and archive parameter cards.
- After render, React re-runs legacy forecast toggle synchronizers so the old checkbox logic keeps working on the new host nodes.
JSON contract returned to React#
The endpoint returns a payload with these top-level parts:
model_summary- selected model metadata shown above the tablessections.inner- inner modelssections.previous- previous modelssections.cluster- cluster-related modelsarchive- normalized archive parameter structure consumed byAnalysisArchiveSection
The archive block is already normalized for the UI: each item carries a display name, description, threshold, numeric progress, and status label. React only renders it and does not recompute archive math on the client.
Archive Parameters pipeline#
Entry point#
The backend entry point is:
analyzeArchiveParameters($modelId, $db, $db_prefix, $model, $branches, $paramsTableExists)
This function prepares the visible parameter set according to the model type (AM, DBM, EM, EAM, AM/DBM) and then calculates the progress for each parameter.
Visible item groups#
All models start from a shared base set:
TIME_DEPTHAPPROACH_LEVELAIM1toAIM4
Additional items depend on the model family:
EM/EAMaddOVERCOME_LEVEL,REVSTOP_LEVEL, and optionallyAIM5toAIM8AM/AM/DBMaddLOST_AMDBMaddsREACHED
Unified history engine#
The current target logic is centered around:
buildUnifiedAnalysisAimHistory(array $model, array $params, array $controls, array $levels, $db, $db_prefix)
This helper scans chart history in model-relative phases and acts as the single visible source of truth for:
- whether approach was reached
- how much direct progress
AIM1-AIM4accumulated - whether breakdown/overcome was reached
- how much reverse progress
AIM5-AIM8accumulated - whether reverse stop invalidated further breakdown-target rendering
This is the key reason the tab now behaves consistently: the visible cards and visible aim bars no longer depend on separate ad-hoc formulas.
CP and size resolution#
Before the history scan can work, the server must resolve CP_level, CP_bar, and model size.
The main helper is:
resolveAnalysisCpContext($model, $params, $controls, $levels, $db, $db_prefix)
Resolution order is intentionally aligned with real stored data:
- try
calcP6/calcP6t - if unavailable, restore CP/breakdown context from stored levels such as
lvl_am,lvl_brkd,P6, orSL
This is an example of a justified storage-based fallback. It exists because different generations of models persist equivalent control data in different places.
Time Depth alignment#
Time Depth is no longer calculated from an isolated legacy-only path.
The current implementation of calculateTimeDepthProgress() first tries to reuse the unified history snapshot and, if needed, restores the same core inputs through the shared helpers:
resolveAnalysisCpContext()resolveAnalysisLifecycleEndAbs()- archived
size_timeor reconstructed model time points
This keeps TIME_DEPTH aligned with the same recovered CP_bar, lifecycle-end boundary, and model-size semantics that now drive Approach, Overcome, and visible target progress.
Parameter calculators#
calculateParameterProgress() dispatches the visible archive items to dedicated calculators:
calculateTimeDepthProgress()calculateApproachProgress()calculateOvercomeProgress()calculateRevStopProgress()calculateLostAMProgress()calculateReachedProgress()calculateAimProgress()
The important design rule is that visible target bars are fed from the unified history engine, while type-specific scalar items may still use narrower calculators where that remains semantically correct.
AIM normalization rules#
After raw calculation, the server normalizes target groups so that a farther target cannot be more complete than an earlier one.
Examples:
AIM3cannot exceedAIM2AIM4cannot exceedAIM3AIM6cannot exceedAIM5, and so on
Archived models may also force the final archived aim to 100% when the final persisted state clearly proves that exact target was reached.
How React renders the archive panel#
AnalysisArchiveSection does not know business rules. It only renders normalized server data.
archive.items- generic parameter cardsarchive.approach+archive.direct_targets- direct phase card andAIM1-AIM4archive.overcome+archive.breakdown_targets- reverse phase card andAIM5-AIM8archive.revstop- substatus inside the overcome group
The progress bar component AnalysisProgressBar is deliberately dumb: it receives a numeric value and only clamps/render-labels it.
The temporary runtime debug block that used to expose internal snapshots has been removed. The production panel now renders only the normalized working payload returned by the backend.
Forecast toggle host#
The block with forecast toggles is intentionally mounted into a stable DOM host:
<div id="analysisForecastTogglesHome">...</div>
This is important because main.js still binds legacy change listeners directly to these checkbox nodes. If the host is remounted during normal Analysis refreshes, the legacy listeners become stale.
Safe extension rules#
- Do not change only the legacy HTML branch in
analysis_ajax.phpand assume the visible tab changed. The production UI uses the React JSON path. - Do not apply HTML escaping to the JSON branch. Keep output hardening local to the legacy HTML rendering path.
- When changing visible labels or panel structure, keep all interface text in English.
- When changing archive math, start from the server side and keep React presentational.
- Preserve the hidden
#collapseAnalysis #modelIdbridge unless you are intentionally redesigning the whole legacy/React integration. - Preserve the stable forecast toggle host unless you also rewrite the legacy checkbox binding model in
main.js. - After editing React or CSS, rebuild the bundle with
npm run buildso the changes reachpublic/app.min.jsandpublic/app.css.
Debugging checklist#
- If the wrong model appears in
Analysis, inspect the hidden bridge flow inmain.jsand the React observer inAnalysisAccordionItem. - If related-model tables are wrong, inspect branch loading in
fetchAnalysisBranches()and id extraction forIDinners,IDprevs, andclusterParams. - If archive progress is wrong, inspect
resolveAnalysisCpContext(),buildUnifiedAnalysisAimHistory(),resolveAnalysisLifecycleEndAbs(), and the final normalization insideanalyzeArchiveParameters(). - If specifically
Time Depthdiverges fromApproach/AIM, check whether the runtime reached the unified-history path or the shared CP/lifecycle recovery path insidecalculateTimeDepthProgress(). - If browser and CLI disagree, verify broker/database context first. The project uses a multibroker runtime and the same
model_idcan point to different data in another context. - If React changes appear not to work, confirm the bundle was rebuilt and the browser is not serving an old
public/app.min.js.
Practical local commands#
Typical local actions while working on the tab:
npm run build
php -l templates/private/analysis_ajax.php