@@ -1765,6 +1765,240 @@ Multiple access methods supported:
17651765</Cell >
17661766```
17671767
1768+ ## Usage History and Analytics
1769+
1770+ ### Overview
1771+ Grid 3 tracks phrase history in SQLite database files stored in the user's data directory. This allows for analytics on what phrases have been spoken, when they were used, and optionally where they were used (if GPS is enabled).
1772+
1773+ ### History File Locations
1774+
1775+ Grid 3 stores history data in user-specific directories:
1776+
1777+ ** Windows:**
1778+ ```
1779+ C:\Users\Public\Documents\Smartbox\Grid 3\Users\{username}\{language}\Phrases\history.sqlite
1780+ ```
1781+
1782+ ** Alternative locations:**
1783+ ```
1784+ %USERPROFILE%\Documents\Smartbox\Grid 3\Users\{username}\{language}\Phrases\history.sqlite
1785+ ```
1786+
1787+ ** Structure:**
1788+ ```
1789+ Grid 3\
1790+ └── Users\
1791+ ├── {username1}\
1792+ │ └── en-GB\
1793+ │ └── Phrases\
1794+ │ └── history.sqlite
1795+ └── {username2}\
1796+ └── en-GB\
1797+ └── Phrases\
1798+ └── history.sqlite
1799+ ```
1800+
1801+ Each user can have multiple language directories (e.g., ` en-GB ` , ` en-US ` , ` fr-FR ` ), each with its own history database.
1802+
1803+ ### Database Schema
1804+
1805+ The ` history.sqlite ` file contains two main tables:
1806+
1807+ #### Phrases Table
1808+
1809+ Stores unique phrases with their XML content.
1810+
1811+ ** Schema:**
1812+ ``` sql
1813+ CREATE TABLE Phrases (
1814+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
1815+ Text TEXT NOT NULL ,
1816+ Content TEXT NOT NULL
1817+ );
1818+ ```
1819+
1820+ ** Fields:**
1821+ - ` Id ` - Primary key, auto-increment
1822+ - ` Text ` - Plain text version of the phrase (lowercase, for indexing)
1823+ - ` Content ` - XML representation of the phrase with proper casing and formatting
1824+
1825+ ** XML Content Format:**
1826+
1827+ Grid 3 stores phrases as XML with ` <r> ` (run) elements containing text fragments:
1828+
1829+ ``` xml
1830+ <p ><s ><r >I</r ></s ><s ><r ><![CDATA[ ]]> </r ></s ><s ><r >love</r ></s ><s ><r ><![CDATA[ ]]> </r ></s ><s ><r >you</r ></s ></p >
1831+ ```
1832+
1833+ This represents: "I love you"
1834+
1835+ The XML structure preserves:
1836+ - Proper capitalization
1837+ - Spacing between words
1838+ - Sentence structure
1839+ - Symbol associations (if any)
1840+
1841+ #### PhraseHistory Table
1842+
1843+ Stores each occurrence of a phrase being spoken.
1844+
1845+ ** Schema:**
1846+ ``` sql
1847+ CREATE TABLE PhraseHistory (
1848+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
1849+ PhraseId INTEGER NOT NULL ,
1850+ Timestamp BIGINT NOT NULL ,
1851+ Latitude REAL ,
1852+ Longitude REAL ,
1853+ FOREIGN KEY (PhraseId) REFERENCES Phrases(Id)
1854+ );
1855+ ```
1856+
1857+ ** Fields:**
1858+ - ` Id ` - Primary key, auto-increment
1859+ - ` PhraseId ` - References ` Phrases.Id `
1860+ - ` Timestamp ` - When the phrase was spoken (.NET ticks: 100-nanosecond intervals since 0001-01-01)
1861+ - ` Latitude ` - GPS latitude (if location tracking enabled, otherwise NULL)
1862+ - ` Longitude ` - GPS longitude (if location tracking enabled, otherwise NULL)
1863+
1864+ ### Querying History Data
1865+
1866+ ** Get all unique phrases that have been spoken:**
1867+ ``` sql
1868+ SELECT DISTINCT p .Content
1869+ FROM PhraseHistory ph
1870+ INNER JOIN Phrases p ON p .Id = ph .PhraseId
1871+ WHERE ph .Timestamp <> 0
1872+ ORDER BY ph .Timestamp DESC ;
1873+ ```
1874+
1875+ ** Note:** Phrases with ` Timestamp = 0 ` exist in the database but have never been spoken.
1876+
1877+ ** Get phrase usage with timestamps:**
1878+ ``` sql
1879+ SELECT
1880+ p .Content ,
1881+ ph .Timestamp ,
1882+ ph .Latitude ,
1883+ ph .Longitude
1884+ FROM PhraseHistory ph
1885+ INNER JOIN Phrases p ON p .Id = ph .PhraseId
1886+ WHERE ph .Timestamp <> 0
1887+ ORDER BY ph .Timestamp ASC ;
1888+ ```
1889+
1890+ ** Get most frequently used phrases:**
1891+ ``` sql
1892+ SELECT
1893+ p .Content ,
1894+ COUNT (* ) as UsageCount,
1895+ MIN (ph .Timestamp ) as FirstUsed,
1896+ MAX (ph .Timestamp ) as LastUsed
1897+ FROM PhraseHistory ph
1898+ INNER JOIN Phrases p ON p .Id = ph .PhraseId
1899+ WHERE ph .Timestamp <> 0
1900+ GROUP BY ph .PhraseId
1901+ ORDER BY UsageCount DESC
1902+ LIMIT 20 ;
1903+ ```
1904+
1905+ ** Get phrases with GPS data:**
1906+ ``` sql
1907+ SELECT
1908+ p .Content ,
1909+ ph .Timestamp ,
1910+ ph .Latitude ,
1911+ ph .Longitude
1912+ FROM PhraseHistory ph
1913+ INNER JOIN Phrases p ON p .Id = ph .PhraseId
1914+ WHERE ph .Latitude IS NOT NULL
1915+ AND ph .Longitude IS NOT NULL
1916+ AND ph .Timestamp <> 0
1917+ ORDER BY ph .Timestamp DESC ;
1918+ ```
1919+
1920+ ### Parsing XML Content
1921+
1922+ To extract the properly cased text from the XML ` Content ` field:
1923+
1924+ ** JavaScript/TypeScript:**
1925+ ``` javascript
1926+ function parseGrid3XML (xmlContent ) {
1927+ // Extract all <r>...</r> content, including CDATA sections
1928+ const rTagRegex = / <r>(?:<!\[ CDATA\[ )? (. *? )(?:\]\] >)? <\/ r>/ g ;
1929+ const matches = [];
1930+ let match;
1931+
1932+ while ((match = rTagRegex .exec (xmlContent)) !== null ) {
1933+ matches .push (match[1 ]);
1934+ }
1935+
1936+ return matches .join (" " );
1937+ }
1938+ ```
1939+
1940+ ** Example:**
1941+ ``` javascript
1942+ const xml = ' <p><s><r>I</r></s><s><r><![CDATA[ ]]></r></s><s><r>love</r></s></p>' ;
1943+ const text = parseGrid3XML (xml);
1944+ // Returns: "I love"
1945+ ```
1946+
1947+ ### Converting .NET Ticks to Dates
1948+
1949+ Timestamps are stored as .NET ticks (100-nanosecond intervals since 0001-01-01 00:00:00).
1950+
1951+ ** Conversion Formula:**
1952+ ``` javascript
1953+ function dotNetTicksToDate (ticks ) {
1954+ const epochTicks = 621355968000000000 ; // Ticks at Unix epoch (1970-01-01)
1955+ const ticksPerMillisecond = 10000 ;
1956+ const milliseconds = (ticks - epochTicks) / ticksPerMillisecond;
1957+ return new Date (milliseconds);
1958+ }
1959+ ```
1960+
1961+ ** Example:**
1962+ - Ticks: ` 638000000000000000 `
1963+ - Converts to: ` 2023-01-01 00:00:00 UTC `
1964+
1965+ ### GPS Location Data
1966+
1967+ If location tracking is enabled in Grid 3 settings, the ` Latitude ` and ` Longitude ` fields will contain GPS coordinates.
1968+
1969+ ** Features:**
1970+ - Track where phrases were spoken
1971+ - Analyze communication patterns by location
1972+ - Useful for community-based AAC users
1973+ - Privacy considerations: GPS data is sensitive
1974+
1975+ ** Checking for GPS data:**
1976+ ``` sql
1977+ SELECT
1978+ COUNT (* ) as TotalPhrases,
1979+ SUM (CASE WHEN Latitude IS NOT NULL THEN 1 ELSE 0 END) as PhrasesWithGPS
1980+ FROM PhraseHistory
1981+ WHERE Timestamp <> 0 ;
1982+ ```
1983+
1984+ ### Important Notes
1985+
1986+ 1 . ** Multiple users** - Each Grid 3 user has their own history database
1987+ 2 . ** Multiple languages** - Each language has its own history database
1988+ 3 . ** Timestamps are .NET ticks** - Must convert to standard dates for analysis
1989+ 4 . ** Zero timestamps** - Phrases with ` Timestamp = 0 ` have never been spoken
1990+ 5 . ** XML parsing required** - The ` Content ` field contains XML that must be parsed to get readable text
1991+ 6 . ** Privacy considerations** - History data may contain sensitive personal information
1992+ 7 . ** File access** - Grid 3 may lock the database file when running; close the app to access it
1993+
1994+ ### Chat History vs Phrase History
1995+
1996+ Grid 3 distinguishes between:
1997+ - ** Phrase History** - All phrases spoken through the "Speak" command (stored in ` history.sqlite ` )
1998+ - ** Chat History** - Messages in the chat workspace (may be stored separately or in workspace state)
1999+
2000+ The ` history.sqlite ` file specifically tracks phrases that were spoken aloud, not just typed into workspaces.
2001+
17682002## Best Practices
17692003
17702004### Cell Design
0 commit comments