Pulse — technical guide
For maintainers tracing Pulse configuration, shared-link responses and scheduled communication. Implementation statements are Verified in code in the local legacy checkout; deployed parity is Not verified. See Functional and Configuration for customer-facing behavior and setup.
Architecture and component responsibilities
Pulse uses legacy Symfony controllers, Twig, jQuery/Ajax and Doctrine. Paths in this page are relative to smartmobility-legacy unless another repository is named.
Company account → Setting → Pulse campaign + Assessment(type=pulse)
├─ PulseUser → User / School (invitation population)
├─ Email(entity=pulse) → scheduled command → mail transport
└─ shared hash link → external questionnaire → PulseForm
└─ PulseFormAnswer → AssessmentQuestion
Company summary → participant count + form count + dates/durations
| Component | Responsibility |
|---|---|
src/Company/PulseBundle/Controller/DefaultController.php | List/detail, immediate draft creation, information updates, questionnaire assignment, population and email planning. |
src/Company/PulseBundle/Resources/views/Default/ | Campaign list/summary and wizard; assessment.html.twig enforces selector lock in the UI. |
src/External/PulseBundle/Controller/DefaultController.php | Resolve shared campaign hash, gate page entry and persist submitted forms/answers. |
src/External/PulseBundle/Resources/views/Default/test.html.twig | Render questionnaire types, collect answers/duration, Ajax submit. |
src/Main/ConfigBundle/Entity/Pulse*.php and corresponding Doctrine mappings | Campaign, invitation, response and answer persistence. |
src/Command/Notifications/EmailProgramationCampaignCommand.php | Dispatch planned emails for published Pulse campaigns. |

The questionnaire step restricts its Assessment selector to Pulse forms and currently has no selected questionnaire. An eligible Pulse form must be available before attaching it; the empty selector does not show a respondent survey. Observed in application: the legacy campaign wizard is accessible from the company area although Pulse is absent from the visible Performance menu.
Request and data flow
config/routes.yaml mounts company routes at /company/pulse and external routes at /external/form. The following is the browser-facing legacy contract, not a separately versioned public API. Route YAML generally does not constrain HTTP methods except the external entry route; methods below describe caller/handler behavior.
| Operation | Route suffix / request | Result |
|---|---|---|
| List / summary | Company /{id}, optional ID | Tenant list or tenant-scoped campaign; missing campaign redirects to list. |
| Create | Company /new, navigated from an add link | Immediately persists Draft and redirects to /parameters/{id}. |
| Save information | POST company /update | Symfony form plus campaign fields; JSON success or JSON validation error. |
| Questionnaire | POST company /assessment, pulse, assessment | Assigns Assessment; missing entities throw not-found. |
| Population | POST company /userupdate, pulse, collaborateur[] | Synchronizes a non-empty selection, creates missing memberships; end-date precondition can return HTTP 412. |
| Planned mail | Company /notifications/{id}, form event=create | Persists an Email, then renders the planning page. |
| Survey entry | GET external /{hash} | Shared questionnaire or unavailable/already-completed page. |
| Submit | Ajax external /success | Saves response/answers; non-Ajax returns HTTP 400. |
Illustrative information-update payload (form token omitted; use the rendered form and its CSRF token):
pulse[pulse_id]=<campaign-id>
nameCampaign=Team check-in
descriptionCampaign=Short survey
statusCampaign=brouillon
start_date=02/11/2026&start_hour=09:00
end_date=09/11/2026&end_hour=18:00
Successful mutation handlers commonly return {"response":"success"}. Information validation can return {"response":"error","message":"Erreurs de validation","errors":["…"]} without an explicit non-200 status. Callers must inspect the body; the information template's success callback does not establish that every persistence error is surfaced correctly.
External submission carries pulse=<shared-hash>, duration=<displayed-duration>, and answers named name_<question-id>. Multi-select uses arrays; item choices can use name_<question-id>_<item-id>. Scalar answers become one PulseFormAnswer, arrays become multiple answer rows, and empty select answers can create null responses. These are implementation examples, not permission to bypass the rendered questionnaire.
Data model and lifecycle
| Entity | Stored relationships / fields |
|---|---|
Pulse | Setting, owning School, Assessment; name, description, target, status, creation/start/end timestamps, shared hash; collections of users and forms. |
PulseUser | Pulse, User, School; updated timestamp, done flag/date and hash. Creation initializes done to false. |
PulseForm | Pulse; creation timestamp, duration, generated response hash; answers collection. No direct User relation in the mapping. |
PulseFormAnswer | PulseForm and AssessmentQuestion; nullable text response. |
New campaign creation writes before the information form is completed. updateAction scopes campaign lookup by current Setting, handles the Symfony form, parses d/m/Y H:i using the server default timezone, then persists campaign metadata.
The external GET checks missing dates, future start/draft, past end and a campaign cookie. The save handler checks Ajax and the completion cookie, writes a 30-day cookie, creates a form and persists answers. It does not mark a participant done in this path. Persistence flush occurs inside the branch for questionnaires with questions; an empty questionnaire must not be assumed to produce a durable response.
Population synchronization only executes when collaborateur is non-empty. An empty request is not a verified way to remove everyone. Invitation counts and response counts are separate collections and should not be converted into per-person completion assertions.
Configuration, permissions and legacy coexistence
The navigation gate is module_pulse_bo plus module_pulse_view or the empty-permission fallback. Creation controls check pulse_create; summary edit controls also allow the root company. Many screen lookups compare campaign Setting with the current company. These checks differ across actions; menu visibility alone is not evidence of complete authorization enforcement.
The questionnaire picker queries current-tenant Assessments of type pulse. Its template locks selection after start/publication or when forms exist; the assignment handler does not repeat that lock or explicitly re-check the selected Assessment's type/tenant in the inspected method. Treat the lock as presentation behavior.
The source search found no dedicated Pulse survey controller/module in nextgen-back, nextgen-front or AIHub. No AI calls, inference job or migration bridge were traced for this feature. This documents the inspected implementation, not an assertion about every deployment.
Runtime, notifications and observability
Runtime dependencies are the legacy Symfony application, Doctrine database, Twig/jQuery assets and questionnaire definitions. Planned emails also require the configured mail transport and scheduled execution of EmailNotification:Campaign; scheduler frequency and deployment wiring are Not verified.
The command fetches due planned mail through EmailRepository::getCurrentScheduledEmail(). sendPulseEmail loads only Published campaigns, deduplicates participant email addresses, replaces first/last-name placeholders, sends HTML mail and sets sendedDate. The traced method does not apply completion-based filtering or gate on the survey response window. Investigate due-mail selection, publication, population, mail transport and sent timestamp when diagnosing delivery. Do not run the command against a live population as a documentation check.
The live email editor advertises [[formLink]], while the inspected sender explicitly substitutes only [[firstname]] and [[lastname]]. Any additional placeholder processing is Not verified. Verify delivered content before relying on that placeholder.
Failure modes and maintainer considerations
The following are Verified in code observations, with production impact Not verified:
- Both external entry and save dereference the resolved Pulse before their missing-Pulse check. An unknown hash may fail before the intended friendly error response.
- The external save handler does not repeat the GET's date/status gates or establish participant membership. Do not assume the GET checks secure the write path.
- Duplicate prevention uses a 30-day browser cookie. It is neither identity-backed uniqueness nor a durable guarantee of one answer per person.
- The information form checks non-empty name/date/time values and a 500-character description counter; the Doctrine description mapping specifies 255 characters. Actual schema and save behavior require deployment verification.
- Missing dates/questionnaire, timezone differences, empty questionnaires and inconsistent JSON error handling can produce incomplete or misleading user outcomes.
- Some older table templates reference routes not present in the inspected Pulse route file. A filename alone does not establish a supported reporting/export feature; follow actual routed screens before extending it.
For extensions, keep questionnaire rendering and answer persistence aligned for every question type, enforce write-path authorization/tenant/type checks, and verify response integrity before changing schema or campaign selection rules. These are maintainer recommendations, not implemented guarantees.
Security, privacy and evidence
Invitation records identify employees; external response records do not directly reference a user, but include timestamps, duration and answer content. Treat shared campaign links as access-bearing content, keep real hashes out of documentation and logs, and avoid claims of guaranteed anonymity or per-person completion. Campaign-to-users/forms and form-to-answers mappings use cascade: [all]; no restore path was verified for deletion.
Sources inspected: the component paths above; config/routes.yaml; both Pulse bundle Resources/config/routing.yml files; src/Main/ConfigBundle/Resources/config/doctrine/Pulse*.orm.yml; src/Company/PulseBundle/Form/Type/PulseType.php; web/assets/js/pulse-characters-limitation.js; app/Resources/views/core_company_theme_v3.html.twig.
Observed in application, 25 October 2026: stayed on PS Testing 2026 as Jim Carter. Pulse was absent from the visible Performance menu, but /company/pulse opened Engagement campaign Pulse and its No campaign available... state. Created and saved Documentation - Pulse - 2026-10-25 (campaign 331), retained Draft, and reopened it to verify persistence. The summary showed zero participants, zero responses and no questionnaire. Participants showed No participants selected yet...; Questionnaires showed an empty Assessment selector restricted to type Pulse; Planification showed No notification scheduled. Opened and dismissed Schedule an email without saving. No participants were added, campaign published or emails scheduled. Respondent submission, delivery and questionnaire locking were not exercised.
Not verified: server/schema versions, scheduler execution, email delivery, response submission, deployed code parity and reporting beyond the observed empty summary.
Functional-guide evidence
These implementation and product references support the functional guide. Local source behavior does not establish which revision or optional features are deployed. Application observations remain distinct from code and documentary evidence.
- Observed in application, 25 October 2026: stayed on PS Testing 2026 as Jim Carter. Pulse was absent from the visible Performance menu, but
/company/pulseopened Engagement campaign Pulse and its No campaign available... state. Created and saved Documentation - Pulse - 2026-10-25 (campaign 331), retained Draft, and reopened it to verify persistence. The summary showed zero participants, zero responses and no questionnaire. Participants showed No participants selected yet...; Questionnaires showed an empty Assessment selector restricted to type Pulse; Planification showed No notification scheduled. Opened and dismissed Schedule an email without saving. No participants were added, campaign published or emails scheduled. Respondent submission, delivery and questionnaire locking were not exercised. - Verified in code:
smartmobility-legacy/src/Company/PulseBundle/Controller/DefaultController.php, itsResources/views/Default/templates, andsrc/External/PulseBundle/Controller/DefaultController.phpplustest.html.twig. - Not verified: deployment parity, delivery of emails, questionnaire analytics, exports, resume/edit after submission, and per-person completion tracking.
Last updated on 3 November 2026