Geography, zones & GPS
The official wilaya and commune reference, sales zones assigned to delegates, the GPS capture standard with distance computation, and optional geocoding.
This area provides the geography every other area relies on (cahier §4.1, §4.5, §6, §6.3, §6.4, §6.5, §8, §11): the official list of wilayas (provinces) and communes used by addresses, filters and reports; the sales zones that group them and are covered by delegates; the single format in which GPS positions are captured and the way distances are computed; and, if HOCO wants it, address geocoding through the maps provider. The server's geo module owns Wilaya, Commune, Zone and ZoneArea and the distance helpers; the delegate app (DA) captures positions, the back-office (BO) edits zones, and every client reads the reference lists.
| ID | Requirement | Release | Priority | Source | Apps |
|---|---|---|---|---|---|
GEO-01 | Official wilaya and commune reference (FR/AR names) used by addresses, zones and filters | V1 | MUST | §4.1, §6.5 | API · BO · RA · RW · DA |
GEO-02 | Zones grouping wilayas and communes, assigned to delegates | V1 | MUST | §4.5, §6 | API · BO · DA |
GEO-03 | GPS capture standard (lat/lng, accuracy, timestamp) and distance computation | V1 | MUST | §6.3, §6.4, §8 | API · DA |
GEO-04 | Address geocoding / reverse geocoding through the maps provider | V1 | COULD | §11 | API · DA · BO |
Settings used in this area
| Setting | Default | Meaning |
|---|---|---|
field.gpsToleranceMeters | 150 | Maximum distance between the check-in fix and the point of sale (FLD-07, D-06). |
field.gpsMaxAccuracyMeters | 100 | A fix less precise than this is not used for a check-in. |
The tolerance and accuracy values are owned by field operations; they are listed here because GEO-03 defines how they are applied.
Requirements
GEO-01 — Wilaya and commune reference
Statement — The system shall provide the official list of wilayas and communes, with French and Arabic names, and use it for every address, zone and geographic filter.
Rules
- The reference contains every wilaya and commune of the official administrative division in force when the seed is built — 58 wilayas and 1 541 communes in the 2019 division, to be re-checked against the latest official list before seeding. A wilaya has a
code(two-digit string"01"–"58", the official number) andnameFr/nameAr; a commune has a UUIDid, its official code, itswilayaCode,nameFr/nameArand a centroid (used for maps and to resolve a guest's support region from the device position — SUP-05). - The reference is read-only through the API —
reference.wilayasandreference.communes, public, cacheable withETagfor one day — and changes only through a seed migration (SRV-02); it is not editable in the back-office. - Every address or location field (store address, delivery addresses, zones, warehouses) references a commune id and its wilaya; free-text wilaya or commune values are not accepted. A commune that does not belong to the given wilaya returns
400 VALIDATION_FAILEDwith field codeCOMMUNE_NOT_IN_WILAYA. - Names follow
Accept-Language(French in V1, Arabic prepared for RTL). Wilayas are listed by code (« 16 — Alger »), communes alphabetically; pickers search without accents or case (bejaia finds Béjaïa). - The delegate app stores the whole list locally and refreshes it with
If-None-Match, so the new-reseller form works offline (FLD-11). - Filters across CRM, orders, dashboards and reports use the same codes and ids, so a wilaya filter means the same thing everywhere.
- Imports (ADM-06) match a wilaya by code or name and a commune by name within that wilaya, ignoring accents and case; an unmatched value is a row error
UNKNOWN_COMMUNE.
Acceptance criteria
reference.wilayasreturns exactly the seeded official list (58 entries in the 2019 division), each with code and both names.- A second request with the returned
ETaginIf-None-Matchanswers304 Not Modified. - Creating an address with wilaya
16and a commune of wilaya31returns400 VALIDATION_FAILEDwithCOMMUNE_NOT_IN_WILAYA. - With
Accept-Language: ar, the names are returned in Arabic. - Typing bejaia in the wilaya picker of DA-14 finds 06 — Béjaïa, with the phone in airplane mode.
Data — Wilaya, Commune, ResellerAddress, ZoneArea, Warehouse.
Interfaces
- Screens: RA-05 Sign-up — store details, customer type & password · RA-19 Address book & address form
- API:
reference.wilayasGET /reference/wilayas·reference.communesGET /reference/wilayas/{wilayaCode}/communes·addresses.createPOST /reseller/addresses
Notes / open points — Algeria's administrative map has changed before (2019); the seed is versioned so a future reform is a data migration, not a code change. The list to use is confirmed with HOCO before go-live (see the decision register).
GEO-02 — Zones grouping wilayas and communes
Statement — The system shall let the back-office define sales zones made of whole wilayas or individual communes, assign delegates to them, and derive each point of sale's zone from its commune.
Rules
- A zone (
Zone: code, name, description, map colour, active flag) is made of areas (ZoneArea), each either a whole wilaya or a single commune. A commune belongs to at most one active zone: saving a zone that would overlap another answers409 ZONE_OVERLAPwith the conflicting communes indetails. - A point of sale's zone defaults to the zone containing its store commune; it is recomputed when the address changes and, by a background job, when zones change. An assignment (CRM-05) may set another zone explicitly, with a reason. A commune outside every zone gives « Hors zone », available as a CRM filter.
- Delegates cover zones through
DelegateZone(several delegates per zone, several zones per delegate). When exactly one delegate covers a commune, the back-office creation form proposes them by default and the validation queue suggests them for unassigned sign-ups (CRM-05); nothing is assigned silently. - Zones are edited in BO-19 (list and map, communes coloured by zone) through
admin.zones.crud, which requiresfield.administer. A zone that still has delegates or points of sale cannot be deleted (409 ZONE_IN_USE); it is deactivated instead. - Changing a zone's communes recomputes the zone of the affected points of sale but never changes their assigned delegate; BO-19 shows how many points of sale now have a delegate who does not cover their zone, with a link to reassign them in bulk.
- The delegate app reads its coverage from
delegate.profile(zones, wilayas, communes) anddelegate.zone(counts of prospects, resellers and active resellers per commune) for DA-25. - Zones feed the delegate scope (ACL-03), the CRM and dashboard filters, and objectives per delegate.
Acceptance criteria
- Zone Oran Ouest made of wilaya 31 cannot be saved alongside zone Oran Centre containing the commune of Oran: the second save returns
409 ZONE_OVERLAPnaming that commune. - A point of sale created in Es Sénia gets the zone that contains Es Sénia, with no input from the user.
- Moving Es Sénia to another zone updates the zone of its points of sale within 5 minutes and leaves their delegate unchanged.
- With Karim the only delegate covering Es Sénia, BO-12 proposes Karim for a new point of sale there, and a self sign-up from Es Sénia without invitation stays unassigned with Karim suggested in BO-13.
- Deleting a zone that still has a delegate returns
409 ZONE_IN_USE. - DA-25 shows Karim's communes with their counts of prospects and resellers.
Data — Zone, ZoneArea, DelegateZone, DelegateProfile, Reseller, Commune, Wilaya.
Interfaces
- Screens: DA-25 My zone · BO-19 Zones (list + map editor)
- API:
delegate.profileGET /delegate/profile·delegate.zoneGET /delegate/zone·admin.zones.crudGET · POST · PATCH · DELETE /admin/zones[/{zoneId}]
GEO-03 — GPS capture standard and distance computation
Statement — The system shall capture every position in one standard format with its accuracy and time, validate it, and compute distances the same way on the server and in the apps.
Rules
- Every position is
{ lat, lng, accuracy, capturedAt }: WGS84 decimal degrees with at least 6 decimals, the horizontal accuracy in metres reported by the device, and the device's UTC time of the fix; Android also sendsisMocked(mock-location flag). - Validation:
latbetween 18.9 and 37.2 andlngbetween −8.7 and 12.0 (Algeria with a margin), otherwise field codeLOCATION_OUT_OF_BOUNDS;accuracygreater than 0;capturedAtat most 5 minutes in the future and, for a check-in, at most 2 minutes older than the action'soccurredAt(field codeLOCATION_STALE— the app takes a fresh fix). - Positions are captured at tour start, at visit check-in, on visit photos, on a new point of sale, in location pings during a tour (FLD-13, FLD-15) and, optionally, at reseller self sign-up.
- Distance between two points is the haversine formula with an Earth radius of 6 371 008.8 m, rounded to the metre. The same function exists in
@hoco/shared(TypeScript) andhoco_maps(Dart) and both pass the same test vectors: (35.6969, −0.6331) → (35.6980, −0.6320) = 158 m; (35.6969, −0.6331) → (35.7000, −0.6300) = 444 m; (35.6969, −0.6331) → (36.7538, 3.0588) = 351.4 km. - A check-in is within tolerance when the distance to the point of sale is at most
field.gpsToleranceMeters(150 m), the accuracy is at mostfield.gpsMaxAccuracyMeters(100 m) and the fix is not mocked. Otherwise the API answers422 GPS_OUT_OF_TOLERANCE(withdetails.distanceMeters,toleranceMeters,accuracyMeters),422 GPS_ACCURACY_TOO_LOW(the app asks the delegate to wait for a better fix) or422 GPS_MOCK_LOCATION. - Path length (tour kilometres, FLD-12) is the sum of haversine distances between consecutive pings whose accuracy is at most 100 m, skipping any ping that implies a speed above 150 km/h from the previous kept ping; it is reported in kilometres with one decimal.
- Lists show straight-line distance (« à 1,2 km »); route distance and ETA come from the maps provider when online (INT-01) and are labelled as estimates.
- Positions are personal data: pings follow the retention of FLD-15 (90 days by default); check-in positions stay with the visit as proof.
Acceptance criteria
- The server and the Dart helper both return 158 m and 444 m for the vectors above.
- A check-in 158 m from the point of sale with accuracy 20 m returns
422 GPS_OUT_OF_TOLERANCEwithdistanceMeters: 158; at 140 m it is accepted. - A check-in with accuracy 180 m returns
422 GPS_ACCURACY_TOO_LOW; a mocked fix returns422 GPS_MOCK_LOCATION. - A position at latitude 48.85 (Paris) returns
400 VALIDATION_FAILEDwithLOCATION_OUT_OF_BOUNDS. - An offline check-in whose fix is 10 minutes older than the action returns field code
LOCATION_STALE. - A trail with one ping 3 km away from its neighbours 30 seconds apart is measured without that ping.
Data — Visit (check-in position, distance), VisitPhoto, LocationPing, Reseller (position), Tour (start and end positions).
Interfaces
- Screens: DA-08 Map & navigation · DA-10 Visit & check-in
- API:
delegate.visits.arrivePOST /delegate/visits/{visitId}/arrive
GEO-04 — Address geocoding and reverse geocoding
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | COULD | cahier §11 | API · DA · BO | — (not scheduled in the plan) |
Statement — The system may suggest addresses from GPS positions and positions from typed addresses through the maps provider, without ever replacing the official wilaya and commune reference.
Rules
- Reverse geocoding: after a GPS capture in the new-reseller form (DA-14) or an address form, the app may pre-fill the address line and propose a commune from the provider's answer; the wilaya and commune are always confirmed by the user from the official list (GEO-01).
- Forward geocoding in the back-office: an address typed in the reseller form (BO-12) proposes map pins; the user drags the pin to the shop front. The stored position records its source —
GPS_FIELD,GEOCODEDorMANUAL_PIN— so a geocoded pin is never mistaken for a position captured on site. - Calls go through the server so provider keys are not shipped in the apps (only the map display key is), results are cached 30 days per query or rounded coordinate, and each user is limited to 60 calls per minute; usage stays within the quotas HOCO validated (INT-07).
- When the provider is down or the quota is exhausted, the forms work without suggestions.
- The V1 API catalogue has no geocoding operation: if HOCO confirms this COULD, the contract gains a reverse-geocode and a geocode operation before any client work starts.
Acceptance criteria
- With geocoding enabled, capturing a position in DA-14 pre-fills the address line and proposes the commune, which the delegate can change.
- A pin placed from a typed address is saved with source
GEOCODEDand appears with a distinct marker on the CRM map. - With the provider unreachable, DA-14 and BO-12 still save a point of sale.
Data — Reseller (position and its source), ResellerAddress (position).
Interfaces
- Screens: none dedicated
- API: no dedicated operation
Notes / open points — Not scheduled in the plan (COULD). The provider choice (Google Maps or Mapbox) and its costs are open (see the decision register).
Error codes
| Code | HTTP | Raised when |
|---|---|---|
ZONE_OVERLAP | 409 | A commune would belong to two active zones |
ZONE_IN_USE | 409 | Deleting a zone that still has delegates or points of sale |
GPS_OUT_OF_TOLERANCE | 422 | Check-in farther than the tolerance |
GPS_ACCURACY_TOO_LOW | 422 | Fix less precise than field.gpsMaxAccuracyMeters |
GPS_MOCK_LOCATION | 422 | The device reports a mocked position |
Field codes in 400 VALIDATION_FAILED | 400 | COMMUNE_NOT_IN_WILAYA, LOCATION_OUT_OF_BOUNDS, LOCATION_STALE, import row code UNKNOWN_COMMUNE |
Audit trail & history
The append-only activity journal of sensitive actions, the business histories kept on orders, stock, prices and resellers, and the back-office viewer.
Resellers & prospects (CRM)
Reseller and prospect records, creation paths, unique normalised phone, the ten-status lifecycle, assignment, segments, timeline, follow-ups, validation, documents, list, map and the 360° page.