v1.99 Longer sample
This is the summary of a very long blog post,
Use a <!-- truncate --> comment to limit blog post size in the list view.
This is the summary of a very long blog post,
Use a <!-- truncate --> comment to limit blog post size in the list view.
Following from conversation about StudentTasks, a technology test was completed to uncover issues and benefits in the requirement to allow video uploads, with later playback (markers and external). In the same way as documents, this needs to allow the re-submission of a file until the task is 'locked'.
Because the upload can be a long running process, then upload status needs to be visible while it is happening, and then encoding to straming formats takes place service-side, and so there is a requirement for the UI to show the stages of progress up to the point of completion. From that point, with the record in locked state the UI would show both a thumbnnail of the video, and button elements to view the video using the service player.
At this stage, all testing was done with a small (32sec) video, but later testing needs to work out timings for upload, encoding, and the storage implications of a years worth of full-size video.
After previous investigation conclusions, this test was done with Bunny -https://bunny.net. Their Stream platform is fully API driven and so highly suitable for the purpose. A playbook is written and fully tested.
Direct upload to Bunny ensure video bytes never touch our server, we only handle metadata and progress or status information. The player format is simple > https://player.mediadelivery.net/embed/{libraryId}/{videoId}
Investigation was made into using signed links for video retrieval but honestly wasted a lot of time on something which still unable to get to work. The files ar enot publicly exposed and the ony way you can get to view is from inside something where you are logged in, so for the moment parked unless there is deemed a need. The player's default behaviours (autoplay, captions, preload) can be set globally in the BunnyStream library dashboard.
Encoding is free on the strndard tier but uses a shared queue. Premium encoding is available for a charge : uses Just-In-Time (JIT) technology — video playable within seconds of upload. Cost at $0.05/minute: estimate of max 1,200 minutes × $0.05 = $60/year maximum. Given that the marker is looking at the task records after they are completed it is unlikley that this would be required.
Testing with a 32-second 64MB MOV file completed end-to-end in under 6 minutes, testing with more realistic file will give better benchmarks. This does affect the ability of the student to view the uploaded and encoded file, discussion needed.
A student on a mobile device (e.g. recording placement footage on a phone) could receive an SMS or email containing a link to a minimal, standalone upload page from inside the Digifolio. The link contains enough context to identify the StudentTask record without requiring full portal authentication, to be able to view the video. This would be linked to a one-time or short-lived token.
Following from conversation about StudentTasks, a technology test was completed to create a service to be able to ansyncronously retrieve a base64 encoded document from a FileMaker record. This would be triggered after a submit action finalises a student task submission. There is no UI requirement, so status information becomes key to confirm completion, failure or stalled extractions. Paramters are {taskID, fileType}
{
"text": "extracted plain text content",
"charCount": 17961,
"durationMs": 209,
"library": "mammoth",
"version": "1.8.0",
"filename": "report.docx",
"lineBreaks": "single",
"hash": "c99b32954d120bf62ad818944660275f"
}
This has been extended to provide a micro-service, which can take parameters of type and base64encoded file, along with fileName and returnHash. As this is an open endpoint we shall be adding a state or session parameter to reduce fake attempts.
cURL https://server/extraction/api/extract/direct
{"b64": "${B64}", "fileType": "pdf", "fileName": "test4.pdf", "returnHash": true}
{
"text": "extracted text",
"charCount": 2115,
"durationMs": 241,
"library": "mammoth",
"version": "1.12.0",
"filename": "test.docx",
"hash": "c99b32954d120bf62ad818944660275f"
}
Response is very fast at sub 300ms for 4 page test docx file, and this covers:
A playbook is written and fully tested.
If questions are prefixed with a known character (§) then the text can be extracted with singke carriage returns and then substitute extra lines before teh character for presentation purposes. The extraction is written as a service, so could be called from other places in the FileMaker ecosphere.
Added frameworks and set up for aditional translation support for Supervisor tutotrial pages. Basic supervisor and student docs pages are now in languages.
Decision needed on which Serbo-Croation flavours to support.
Installed the AWS translation code into Sopley_portfolio on the altdb server, to make it more widely available. Paste the text to be translated into the left field, select the language for the output text, and push the translate button. Once the translation is returned, the copy button will place the output text on the clipboard. You may, of course, copy parts of the output.
There is an indication of the length of the text in the centre, along with an indication of the cost of the action. AWS charges $50 per million characters.

This is a pseudo-live clickable demo, the code equivalent of wireframes, which serves as a design exploration, decision tree, and technology test all-on-one. From the placment list this outlines the behaviour after a students selects Tasks.
A list of Tasks is presented, with indicators for deadline date, late, and locked. If a user selects a task row, it replaces the placement list with the survey view in place — no page navigation, top navigation stays but back nav button changes label and action. If there is an stored data returned from the query initiated on clicking the row, client code always injects authoritative values for these fields, overriding anything in locally stored data.
The student enters data or selects form a presented choice, and at the footer are three buttons. Save will perform and explicit commit of data (back to FileMaker). Clear will remove all current values from form. Submit will show dialog confirming action, and then both save data and add the finalCommit value. For students, all buttons are hidden when isLocked === true.
The Student + open state only — record is debounced at 1500ms after last keystroke to trigger autosave. No autosave for supervisor, marker, or any finalised record. (autocheck needs to only be triggered if there is data, so after clear, it will not overwrite savedData ??)
studentID, moduleID, superID remain as hidden questions inside the surveyJsonBecause this is a combination of React and SurveyJS, there are behaviours that can be driven from the client as well as the JSON definition. They work at different levels: SurveyJS JSON conditions handle question-level behaviour, React/client code handles the survey-level behaviour in TaskClient.tsx - this the important one for the locked/finalised case (this needs to be always for external, and for student if locked) — which puts the entire SurveyJS instance into a read-only display mode regardless of any individual question conditions. No inputs are rendered, everything becomes plain text.
if (isStudent && task.state !== "locked" || isExternal) {
survey.mode = "display";
}
The desired behaviour for supervisor and marker requirements need finalising, and a decision about visibility to students of supervisor and/or marker comments.
A TEMPLATE Task is created, with one of each role of question and notes about visibility conditions. Creators start from there and duplicate questions as required, which keeps the settings & conditions. This is easy because you can simply change th question type from the drop-down.
| Prefix | Who | Behaviour |
|---|---|---|
Q1, Q2 etc | Student questions | enableIf: "{superID} empty" |
SQ1, SQ2 etc | Supervisor questions | enableIf: "markerID} empty", visibleIf: "{superID} notempty" |
MQ1, MQ2 etc | Marker questions | visibleIf: "{markerID} notempty" |
studentID, moduleID, superID, markerID | Hidden carriers | Always injected by client |
Echo360 has an API, which allows us to manage User accounts (and media). We have tested the API, and have scripts running in FileMaker to query Student users, based on Moorlands Email address. There is also a script to create a user, and Echo then sends them an email invite to the platform. We need to investigate what happens with the MCEE students and time zones on sign-up.
The access token for the API is valid for 1 hour, after which the refresh token can be used to retrieve another, along with a new refresh token. Each refresh token may only be used once, so it there is some failure, the process needs to use the client-credentials workflow as a fallback.
The creation of the Echo360 account could be scripted as part of the initial student comms from Technical Support. With API access, the User accounts could then be removed as part of the end-of year or leaving process.
It may be that using the User account to hook to any video uploads is not the right thing to do, as the Student:
Next investigations will cover the mechanics of uploads through the DigiFolio, but sent to a holding account, which ensures that students can not see anyone else's work (but also their own)
The Echo360 public API (/public/api/v1/medias) does not expose raw file download URLs for media. The /medias endpoint returns metadata about media items (title, ID, duration, owner, status, etc.), but there is no field in the response that gives you a direct link to the underlying MP4 file. Echo360's architecture deliberately abstracts away the raw files — they're stored in S3 behind authenticated, signed URLs that are generated on-the-fly by the player, not exposed through the public API.
There are plenty of alternatives for S3 compatible storage, which could provide a signed (time-limited) download link, but the better option might be through a site that offers a stremaing viewer, which also implies a need for encoding. Alternative to investigate is BunnyStream. The pricing for this is micropriced, based on storage volume and playback traffic, so needs some research into the projected volumes.
We have also updated SurveyJS to the latest version, 2.5.13, which includes some bug fixes and performance improvements. We have tested the new version with our existing surveys, and everything seems to be working fine.
We have also made some minor revisions and corrections to our documentation specifications and codebase, which should improve the overall stability and usability of the system.
Investigating how the elements that are web native can be replicated in FileMaker. It is not the same thing, so either we find a way to adapt the web code to show in a single page webviewer or mimic the layout but accept there are some differences in function.
The webcode is not pure js or html, and has a lot of restrictions around login, so feels really too much work to reconstruct just the display elements. The icons from react-lucide are available as SVG downloads, so after some manipulation can be used to match the web UI.
Some old-school tricks here to get highlighting and button visibility to work, but this is (native) FileMaker:

This article assembles the tech tests undertaken, and review notes for investigations inot the best mechanisms to implement the SurevyJS code for StudentTasks within the DigiFolio application under development. While these are presented as a structured document, and might appear as finalised, it is also for the purpose of discussion of the key decisions represented, to ensure that the right architectural direction is taken.
The core of this document is saved as /
This document presents the current design patterns and implementation decisions for the StudentTasks, including multi-role handling, autosave, final submission concept, and review workflows.
Actors:
Design Principles:
savedData) and reviewer/marker comments (reviewerData, markerData)note: marker/tutor may end up with discrete set of tasks fitted into a student task list, and discussion is needed about additional marker interaction with individual StudentTasks.
| Field | Purpose | Update Rules |
|---|---|---|
savedData (JSON) | Student task answers | Updated on Save or Finalise |
lastSubmit | Most recent save timestamp | Updated every save |
finalSubmit | Timestamp of final submission | Updated only on Finalise |
status (enum) | Workflow stage: 0=Open/Active, 1=Submitted, 2=Viewed, 3=Reviwed, 4=Marked?, 5=Examined | Updated only on Finalise or Submit button by Super, Marker, External |
isLate | Derived from finalSubmit > deadlineDate | Auto-calculated |
superData (JSON) | Supervisor/Marker comments | Updated by reviewer/marker |
superId | User ID of Supervisor | Set on review submission |
reviewedAt | Timestamp of review | Set on super submission |
markerData (JSON) | Marker comments | Set by marker |
markerId | Marker ID | Set on moderation |
markedAt (optional) | Timestamp of review | Set on marker submission |
externalData (JSON) | External examiner comments (optional) | Set by external |
externalId | External ID | Set on moderation |
externalAt (optional) | Timestamp of external view of record | Set on external submission |
Student selects placement, then task record
Open/Active
savedData and lastSubmitsurvey.onValueChanged.add(() => {
debounceSave(survey.data);
});
survey.onValueChanged.add((sender) => {
if (currentUser.role === "student") {
autosaveStudentAnswers(sender.data);
}
});
Finalise Submission
finalSubmitstatus => 1 (submitted)isLateif (currentUser.role === "student") {
await fetch("/api/student-task/finalise", {
method: "POST",
body: JSON.stringify({
studentTaskId,
savedData: survey.data
})
});
}
roleif (currentUser.role === "super" || currentUser.role === "marker") {
await fetch("/api/student-task/review", {
method: "POST",
body: JSON.stringify({
studentTaskId,
reviewerData: survey.data,
reviewerId: currentUser.id,
reviewedAt: new Date().toISOString()
})
});
}
reviewerData or markerData is stored separately from savedDataReceives concatenated export snapshot including:
savedData)reviewerData)lastSubmit, finalSubmit, reviewedAt) ??isLate) ??No SurveyJS rendering required, simpler - could also be a PDF format to download?
| Role | Save Target | Autosave? | Submission / Finalise |
|---|---|---|---|
| Student | savedData | Yes | Finalise → sets finalSubmit, triggers webhook |
| Supervisor | reviewerData | No | Manual Submit → sets reviewedAt |
| Marker | markerData | No | Manual Submit → sets markedAt |
| External | N/A (read-only) | N/A | N/A |
┌───────────────────────────────────┐
│ Student Placement Tasks │
└───────────────────────────────────┘
│
▼
┌─────────────────────┐
│ Open/Active. │
│ (savedData updated, │
│ lastSubmit) │
└───────┬─────────────┘
│
Save / Autosave (student only)
│
▼
┌─────────────────────┐
│ Finalise Submission │
│ finalSubmit set │
│ status==1 │
│ isLate evaluated │
└───────┬─────────────┘
│
▼
┌───────────────┐
│ ++ Webhook │
│ Export Trigger│
└───────┬───────┘
│
▼
┌────────────────────────┐
│ Role-Based Review │
└─────────────┬──────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Supervisor │ │ Marker │
│ Review Task │ │ Review Task │
│ (reviewerData │ │ (markerData │
│ + reviewedAt) │ │ + markedAt) │
└───────┬───────┘ └───────┬───────┘
│ │
└───────► Manual Submit ◄┘
│
▼
┌─────────────────────┐
│ Final Marking │
│ Done in FileMaker │
│ from aggregate + │
│ isLate flags + │
│ time records data │
│ transferred to ARLT │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ External Examiner │
│ Receives full export│
│ (student answers + │
│ reviewerData + │
│ timestamps + isLate)│
└─────────────────────┘
Student and reviewer/marker answer data stored separately
savedData → studentreviewerData → supervisormarkerData → marker/tutor/adminAutosave limited to student role
Finalise submission / Submit button
Late flag (isLate)
finalSubmit vs deadlineDate in FileMakerWorkflow metadata tracked via timestamps
lastSubmit → autosave / last student activityfinalSubmit → 'finalised' submissionreviewedAt → supervisor/reviewer submissionmarkedAt → marker comments???External audit/export
SurveyJS 'questions' we refer to a 'Task', comprising one or more questions A collection of Tasks that is against a student, additionally with an academicYear and deadline, is a 'StudentTask' The StudentTask is considered Open until a second button both saves answer data, and 'finalises' the record by adding a second timestamp value. The submitted answers for a StudentTask is available to be reviewed, firstly by 'Supervisor', and subsequently by a 'Marker' If the submission of a Task is after the Deadline, then it is considered 'Late', based on a field which auto-calculates the
By storing the JSON for the questions on the StudentTask records, then the data will match the related questions. If there is a need to modify (say a question wording) then that is on the Task record until pulled into the StudentTask record(s). Structural changes (particularly modifiying question numbers) should therefore ONLY be done mid-year, for Tasks that are not yet near their deadline. To get the Tasks dynamically runs the risk of an update being prepared for another year then being returned to be answered. This also involves two api calls - one for model and one for data.
If status > 0 (finlised), then:
SurveyJS has a Theme Editor which provides a method to preview a modified theme for the/ elements. This is saved as a JSON definition file, which can then be injected into the code for rendering the page, along with the definition JSON.
It does mean (although I am not going to recommend this), that each task, or similar group of tasks, could have their own theme. The following samples show tasks with the default theme over-ridden.
A student may be on a single placement a year for one module only. This is the simplest case to programe and manage. [plain]
They may have more than one placment in a year, each attached to one module. [multi]
They may have a placmement that is attached to two modules, and so the task need to be assigned to the module and the placement. [split]
They may do an additional (short) placment for one module. [extra]
THe case may be that there are combinations of number of placements and whether one or more has more than one mudule attached. [complex]
They may change placment during the year which then remains linked to the same module(s) as the one it replaces. [replace]
There may be tasks which are floating to the module, and so may be completed on any placement associated with that module. [float]
There may be tasks which are floating to the placement, and also floating to either module. [free]
This implies that until a StudentTask has been assigned a module, it is available to all, or all in the same placement, but then after that is becomes fixed for the purpose of marking.
The assemblage of StudentTask records allow for those to be marked and entered into the related ARLT record, which imolies that whether this was completed in a single placement or multiple, these records can be filtered down to just those linked to a specific module.
Because all supervisors and some markers will be using the web front-end, it is desirable to not have to download local copies of files to see their content, and that linked video should be shown in new browser window.
